[pypy-commit] pypy default: merge branch ssa-flow

rlamy noreply at buildbot.pypy.org
Sun Nov 16 20:39:26 CET 2014


Author: Ronan Lamy <ronan.lamy at gmail.com>
Branch: 
Changeset: r74540:a41db2745c80
Date: 2014-11-16 19:39 +0000
http://bitbucket.org/pypy/pypy/changeset/a41db2745c80/

Log:	merge branch ssa-flow

diff --git a/pypy/doc/whatsnew-head.rst b/pypy/doc/whatsnew-head.rst
--- a/pypy/doc/whatsnew-head.rst
+++ b/pypy/doc/whatsnew-head.rst
@@ -47,3 +47,7 @@
 .. branch kill-rctime
 
 Rename pypy/module/rctime to pypy/module/time, since it contains the implementation of the 'time' module.
+
+.. branch: ssa-flow
+
+Use SSA form for flow graphs inside build_flow() and part of simplify_graph()
diff --git a/rpython/annotator/test/test_annrpython.py b/rpython/annotator/test/test_annrpython.py
--- a/rpython/annotator/test/test_annrpython.py
+++ b/rpython/annotator/test/test_annrpython.py
@@ -14,7 +14,6 @@
 from rpython.rlib.rarithmetic import r_uint, base_int, r_longlong, r_ulonglong
 from rpython.rlib.rarithmetic import r_singlefloat
 from rpython.rlib import objectmodel
-from rpython.flowspace.objspace import build_flow
 from rpython.flowspace.flowcontext import FlowingError
 from rpython.flowspace.operation import op
 
@@ -53,17 +52,6 @@
                 self.translator.view()
             return s
 
-    def make_fun(self, func):
-        import inspect
-        try:
-            func = func.im_func
-        except AttributeError:
-            pass
-        name = func.func_name
-        funcgraph = build_flow(func)
-        funcgraph.source = inspect.getsource(func)
-        return funcgraph
-
     def test_simple_func(self):
         """
         one test source:
diff --git a/rpython/flowspace/bytecode.py b/rpython/flowspace/bytecode.py
--- a/rpython/flowspace/bytecode.py
+++ b/rpython/flowspace/bytecode.py
@@ -5,7 +5,6 @@
 from opcode import EXTENDED_ARG, HAVE_ARGUMENT
 import opcode
 from rpython.flowspace.argument import Signature
-from rpython.flowspace.flowcontext import BytecodeCorruption
 
 CO_GENERATOR = 0x0020
 CO_VARARGS = 0x0004
@@ -27,6 +26,11 @@
         kwargname = None
     return Signature(argnames, varargname, kwargname)
 
+
+class BytecodeCorruption(Exception):
+    pass
+
+
 class HostCode(object):
     """
     A wrapper around a native code object of the host interpreter
diff --git a/rpython/flowspace/flowcontext.py b/rpython/flowspace/flowcontext.py
--- a/rpython/flowspace/flowcontext.py
+++ b/rpython/flowspace/flowcontext.py
@@ -7,6 +7,7 @@
 import __builtin__
 
 from rpython.tool.error import source_lines
+from rpython.translator.simplify import eliminate_empty_blocks
 from rpython.rlib import rstackovf
 from rpython.flowspace.argument import CallSpec
 from rpython.flowspace.model import (Constant, Variable, Block, Link,
@@ -16,6 +17,7 @@
 from rpython.flowspace.specialcase import (rpython_print_item,
     rpython_print_newline)
 from rpython.flowspace.operation import op
+from rpython.flowspace.bytecode import BytecodeCorruption
 
 w_None = const(None)
 
@@ -30,11 +32,10 @@
         msg += source_lines(self.ctx.graph, None, offset=self.ctx.last_instr)
         return "\n".join(msg)
 
+
 class StopFlowing(Exception):
     pass
 
-class BytecodeCorruption(Exception):
-    pass
 
 class SpamBlock(Block):
     def __init__(self, framestate):
@@ -79,35 +80,10 @@
         self.last_exception = last_exception
 
 def fixeggblocks(graph):
-    varnames = graph.func.func_code.co_varnames
     for block in graph.iterblocks():
         if isinstance(block, SpamBlock):
-            for name, w_value in zip(varnames, block.framestate.mergeable):
-                if isinstance(w_value, Variable):
-                    w_value.rename(name)
             del block.framestate     # memory saver
 
-    # EggBlocks reuse the variables of their previous block,
-    # which is deemed not acceptable for simplicity of the operations
-    # that will be performed later on the flow graph.
-    for link in list(graph.iterlinks()):
-        block = link.target
-        if isinstance(block, EggBlock):
-            if (not block.operations and len(block.exits) == 1 and
-                link.args == block.inputargs):   # not renamed
-                # if the variables are not renamed across this link
-                # (common case for EggBlocks) then it's easy enough to
-                # get rid of the empty EggBlock.
-                link2 = block.exits[0]
-                link.args = list(link2.args)
-                link.target = link2.target
-                assert link2.exitcase is None
-            else:
-                mapping = {}
-                for a in block.inputargs:
-                    mapping[a] = Variable(a)
-                block.renamevariables(mapping)
-
 # ____________________________________________________________
 
 class Recorder(object):
@@ -132,12 +108,11 @@
 
     def guessbool(self, ctx, w_condition):
         block = self.crnt_block
-        vars = block.getvariables()
         links = []
         for case in [False, True]:
-            egg = EggBlock(vars, block, case)
+            egg = EggBlock([], block, case)
             ctx.pendingblocks.append(egg)
-            link = Link(vars, egg, case)
+            link = Link([], egg, case)
             links.append(link)
 
         block.exitswitch = w_condition
@@ -154,16 +129,16 @@
         links = []
         for case in [None] + list(cases):
             if case is not None:
-                assert block.operations[-1].result is bvars[-1]
-                vars = bvars[:-1]
-                vars2 = bvars[:-1]
                 if case is Exception:
                     last_exc = Variable('last_exception')
                 else:
                     last_exc = Constant(case)
                 last_exc_value = Variable('last_exc_value')
-                vars.extend([last_exc, last_exc_value])
-                vars2.extend([Variable(), Variable()])
+                vars = [last_exc, last_exc_value]
+                vars2 = [Variable(), Variable()]
+            else:
+                vars = []
+                vars2 = []
             egg = EggBlock(vars2, block, case)
             ctx.pendingblocks.append(egg)
             link = Link(vars, egg, case)
@@ -498,33 +473,43 @@
         candidates = self.joinpoints.setdefault(next_instr, [])
         for block in candidates:
             newstate = block.framestate.union(currentstate)
-            if newstate is None:
-                continue
-            elif newstate == block.framestate:
-                outputargs = currentstate.getoutputargs(newstate)
-                currentblock.closeblock(Link(outputargs, block))
-                return
-            else:
+            if newstate is not None:
                 break
         else:
             newstate = currentstate.copy()
-            block = None
+            newblock = SpamBlock(newstate)
+            # unconditionally link the current block to the newblock
+            outputargs = currentstate.getoutputargs(newstate)
+            link = Link(outputargs, newblock)
+            currentblock.closeblock(link)
+            candidates.insert(0, newblock)
+            self.pendingblocks.append(newblock)
+            return
+
+        if newstate.matches(block.framestate):
+            outputargs = currentstate.getoutputargs(newstate)
+            currentblock.closeblock(Link(outputargs, block))
+            return
 
         newblock = SpamBlock(newstate)
+        varnames = self.pycode.co_varnames
+        for name, w_value in zip(varnames, newstate.mergeable):
+            if isinstance(w_value, Variable):
+                w_value.rename(name)
         # unconditionally link the current block to the newblock
         outputargs = currentstate.getoutputargs(newstate)
         link = Link(outputargs, newblock)
         currentblock.closeblock(link)
 
-        if block is not None:
-            # to simplify the graph, we patch the old block to point
-            # directly at the new block which is its generalization
-            block.dead = True
-            block.operations = ()
-            block.exitswitch = None
-            outputargs = block.framestate.getoutputargs(newstate)
-            block.recloseblock(Link(outputargs, newblock))
-            candidates.remove(block)
+        # to simplify the graph, we patch the old block to point
+        # directly at the new block which is its generalization
+        block.dead = True
+        block.operations = ()
+        block.exitswitch = None
+        outputargs = block.framestate.getoutputargs(newstate)
+        block.recloseblock(Link(outputargs, newblock))
+        candidates.remove(block)
+
         candidates.insert(0, newblock)
         self.pendingblocks.append(newblock)
 
@@ -936,6 +921,8 @@
         w_newvalue = self.popvalue()
         assert w_newvalue is not None
         self.locals_stack_w[varindex] = w_newvalue
+        if isinstance(w_newvalue, Variable):
+            w_newvalue.rename(self.getlocalvarname(varindex))
 
     def STORE_GLOBAL(self, nameindex):
         varname = self.getname_u(nameindex)
diff --git a/rpython/flowspace/framestate.py b/rpython/flowspace/framestate.py
--- a/rpython/flowspace/framestate.py
+++ b/rpython/flowspace/framestate.py
@@ -13,20 +13,18 @@
         newstate = []
         for w in self.mergeable:
             if isinstance(w, Variable):
-                w = Variable()
+                w = Variable(w)
             newstate.append(w)
         return FrameState(newstate, self.blocklist, self.next_instr)
 
     def getvariables(self):
         return [w for w in self.mergeable if isinstance(w, Variable)]
 
-    def __eq__(self, other):
-        """Two states are equal
-        if they only use different Variables at the same place"""
+    def matches(self, other):
+        """Two states match if they only differ by using different Variables
+        at the same place"""
         # safety check, don't try to compare states with different
         # nonmergeable states
-        assert isinstance(other, FrameState)
-        assert len(self.mergeable) == len(other.mergeable)
         assert self.blocklist == other.blocklist
         assert self.next_instr == other.next_instr
         for w1, w2 in zip(self.mergeable, other.mergeable):
@@ -35,9 +33,6 @@
                 return False
         return True
 
-    def __ne__(self, other):
-        return not (self == other)
-
     def union(self, other):
         """Compute a state that is at least as general as both self and other.
            A state 'a' is more general than a state 'b' if all Variables in 'b'
@@ -66,14 +61,14 @@
 
 def union(w1, w2):
     "Union of two variables or constants."
+    if w1 == w2:
+        return w1
     if w1 is None or w2 is None:
         return None  # if w1 or w2 is an undefined local, we "kill" the value
                      # coming from the other path and return an undefined local
     if isinstance(w1, Variable) or isinstance(w2, Variable):
         return Variable()  # new fresh Variable
     if isinstance(w1, Constant) and isinstance(w2, Constant):
-        if w1 == w2:
-            return w1
         # FlowSignal represent stack unrollers in the stack.
         # They should not be merged because they will be unwrapped.
         # This is needed for try:except: and try:finally:, though
diff --git a/rpython/flowspace/model.py b/rpython/flowspace/model.py
--- a/rpython/flowspace/model.py
+++ b/rpython/flowspace/model.py
@@ -206,8 +206,6 @@
         return uniqueitems([w for w in result if isinstance(w, Constant)])
 
     def renamevariables(self, mapping):
-        for a in mapping:
-            assert isinstance(a, Variable), a
         self.inputargs = [mapping.get(a, a) for a in self.inputargs]
         for op in self.operations:
             op.args = [mapping.get(a, a) for a in op.args]
diff --git a/rpython/flowspace/objspace.py b/rpython/flowspace/objspace.py
--- a/rpython/flowspace/objspace.py
+++ b/rpython/flowspace/objspace.py
@@ -30,7 +30,7 @@
 
 def build_flow(func):
     """
-    Create the flow graph for the function.
+    Create the flow graph (in SSA form) for the function.
     """
     _assert_rpythonic(func)
     if (isgeneratorfunction(func) and
@@ -41,7 +41,6 @@
     ctx = FlowContext(graph, code)
     ctx.build_flow()
     fixeggblocks(graph)
-    checkgraph(graph)
     if code.is_generator:
         tweak_generator_graph(graph)
     return graph
diff --git a/rpython/flowspace/pygraph.py b/rpython/flowspace/pygraph.py
--- a/rpython/flowspace/pygraph.py
+++ b/rpython/flowspace/pygraph.py
@@ -13,7 +13,7 @@
         from rpython.flowspace.flowcontext import SpamBlock
         data = [None] * code.co_nlocals
         for i in range(code.formalargcount):
-            data[i] = Variable()
+            data[i] = Variable(code.co_varnames[i])
         state = FrameState(data + [Constant(None), Constant(None)], [], 0)
         initialblock = SpamBlock(state)
         super(PyGraph, self).__init__(self._sanitize_funcname(func), initialblock)
diff --git a/rpython/flowspace/test/test_framestate.py b/rpython/flowspace/test/test_framestate.py
--- a/rpython/flowspace/test/test_framestate.py
+++ b/rpython/flowspace/test/test_framestate.py
@@ -26,41 +26,41 @@
         ctx = self.get_context(self.func_simple)
         fs1 = ctx.getstate(0)
         fs2 = ctx.getstate(0)
-        assert fs1 == fs2
+        assert fs1.matches(fs2)
 
     def test_neq_hacked_framestate(self):
         ctx = self.get_context(self.func_simple)
         fs1 = ctx.getstate(0)
         ctx.locals_stack_w[ctx.pycode.co_nlocals-1] = Variable()
         fs2 = ctx.getstate(0)
-        assert fs1 != fs2
+        assert not fs1.matches(fs2)
 
     def test_union_on_equal_framestates(self):
         ctx = self.get_context(self.func_simple)
         fs1 = ctx.getstate(0)
         fs2 = ctx.getstate(0)
-        assert fs1.union(fs2) == fs1
+        assert fs1.union(fs2).matches(fs1)
 
     def test_union_on_hacked_framestates(self):
         ctx = self.get_context(self.func_simple)
         fs1 = ctx.getstate(0)
         ctx.locals_stack_w[ctx.pycode.co_nlocals-1] = Variable()
         fs2 = ctx.getstate(0)
-        assert fs1.union(fs2) == fs2  # fs2 is more general
-        assert fs2.union(fs1) == fs2  # fs2 is more general
+        assert fs1.union(fs2).matches(fs2)  # fs2 is more general
+        assert fs2.union(fs1).matches(fs2)  # fs2 is more general
 
     def test_restore_frame(self):
         ctx = self.get_context(self.func_simple)
         fs1 = ctx.getstate(0)
         ctx.locals_stack_w[ctx.pycode.co_nlocals-1] = Variable()
         ctx.setstate(fs1)
-        assert fs1 == ctx.getstate(0)
+        assert fs1.matches(ctx.getstate(0))
 
     def test_copy(self):
         ctx = self.get_context(self.func_simple)
         fs1 = ctx.getstate(0)
         fs2 = fs1.copy()
-        assert fs1 == fs2
+        assert fs1.matches(fs2)
 
     def test_getvariables(self):
         ctx = self.get_context(self.func_simple)
diff --git a/rpython/flowspace/test/test_generator.py b/rpython/flowspace/test/test_generator.py
--- a/rpython/flowspace/test/test_generator.py
+++ b/rpython/flowspace/test/test_generator.py
@@ -67,7 +67,7 @@
             yield n
             yield n
         #
-        graph = build_flow(func)
+        graph = make_generator_entry_graph(func)
         if option.view:
             graph.show()
         block = graph.startblock
diff --git a/rpython/flowspace/test/test_objspace.py b/rpython/flowspace/test/test_objspace.py
--- a/rpython/flowspace/test/test_objspace.py
+++ b/rpython/flowspace/test/test_objspace.py
@@ -635,6 +635,7 @@
 
     def test_highly_branching_example(self):
         x = self.codetest(self.highly_branching_example)
+        simplify_graph(x)
         # roughly 20 blocks + 30 links
         assert len(list(x.iterblocks())) + len(list(x.iterlinks())) < 60
 
@@ -1290,6 +1291,27 @@
         assert isinstance(link.args[0], Constant)
         assert link.args[0].value == 5
 
+    def test_remove_dead_ops(self):
+        def f():
+            a = [1]
+            b = (a, a)
+            c = type(b)
+        graph = self.codetest(f)
+        simplify_graph(graph)
+        assert graph.startblock.operations == []
+        [link] = graph.startblock.exits
+        assert link.target is graph.returnblock
+
+    def test_not_combine(self):
+        def f(n):
+            t = not n
+            if not n:
+                t += 1
+            return t
+        graph = self.codetest(f)
+        simplify_graph(graph)
+        assert self.all_operations(graph) == {'bool': 1, 'inplace_add': 1}
+
 
 DATA = {'x': 5,
         'y': 6}
diff --git a/rpython/tool/algo/test/test_unionfind.py b/rpython/tool/algo/test/test_unionfind.py
--- a/rpython/tool/algo/test/test_unionfind.py
+++ b/rpython/tool/algo/test/test_unionfind.py
@@ -18,6 +18,17 @@
     uf.find(2)
     for i in xrange(2, 20, 2):
         uf.union(i, 2)
-    assert len(state) == 2 # we have exactly 2 partitions
+    assert len(state) == 2  # we have exactly 2 partitions
 
+def test_asymmetric_absorb():
+    class Info(object):
+        def __init__(self, obj):
+            self.values = [obj]
 
+        def absorb(self, other):
+            self.values += other.values
+
+    uf = UnionFind(Info)
+    uf.union(2, 3)
+    uf.union(1, 2)
+    assert uf[1].values == uf[2].values == uf[3].values == [1, 2, 3]
diff --git a/rpython/tool/algo/unionfind.py b/rpython/tool/algo/unionfind.py
--- a/rpython/tool/algo/unionfind.py
+++ b/rpython/tool/algo/unionfind.py
@@ -72,19 +72,16 @@
         if rep1 is rep2:
             return new1 or new2, rep1, info1
 
-        w1 = self.weight[rep1]
-        w2 = self.weight[rep2]
-
-        w = w1 + w2
-
-        if w1 < w2:
-            rep1, rep2, info1, info2, = rep2, rep1, info2, info1
-
         if info1 is not None:
             info1.absorb(info2)
 
+        w1 = self.weight[rep1]
+        w2 = self.weight[rep2]
+        w = w1 + w2
+        if w1 < w2:
+            rep1, rep2 = rep2, rep1
+
         self.link_to_parent[rep2] = rep1
-
         del self.weight[rep2]
         del self.root_info[rep2]
 
diff --git a/rpython/translator/backendopt/ssa.py b/rpython/translator/backendopt/ssa.py
--- a/rpython/translator/backendopt/ssa.py
+++ b/rpython/translator/backendopt/ssa.py
@@ -16,7 +16,9 @@
         # [Block, blockvar, linkvar, linkvar, linkvar...]
         opportunities = []
         opportunities_with_const = []
-        for block, links in mkinsideentrymap(graph).items():
+        entrymap = mkentrymap(graph)
+        del entrymap[graph.startblock]
+        for block, links in entrymap.items():
             assert links
             for n, inputvar in enumerate(block.inputargs):
                 vars = [block, inputvar]
@@ -123,63 +125,48 @@
 
 # ____________________________________________________________
 
-def mkinsideentrymap(graph_or_blocks):
-    # graph_or_blocks can be a full FunctionGraph, or a mapping
-    # {block: reachable-from-outside-flag}.
-    if isinstance(graph_or_blocks, dict):
-        blocks = graph_or_blocks
-        entrymap = {}
-        for block in blocks:
-            for link in block.exits:
-                if link.target in blocks and not blocks[link.target]:
-                    entrymap.setdefault(link.target, []).append(link)
-        return entrymap
-    else:
-        graph = graph_or_blocks
-        entrymap = mkentrymap(graph)
-        del entrymap[graph.startblock]
-        return entrymap
-
 def variables_created_in(block):
-    result = {}
-    for v in block.inputargs:
-        result[v] = True
+    result = set(block.inputargs)
     for op in block.operations:
-        result[op.result] = True
+        result.add(op.result)
     return result
 
 
-def SSA_to_SSI(graph_or_blocks, annotator=None):
+def SSA_to_SSI(graph, annotator=None):
     """Turn a number of blocks belonging to a flow graph into valid (i.e. SSI)
     form, assuming that they are only in SSA form (i.e. they can use each
     other's variables directly, without having to pass and rename them along
     links).
-
-    'graph_or_blocks' can be a graph, or just a dict that lists some blocks
-    from a graph, as follows: {block: reachable-from-outside-flag}.
     """
-    entrymap = mkinsideentrymap(graph_or_blocks)
-    builder = DataFlowFamilyBuilder(graph_or_blocks)
+    entrymap = mkentrymap(graph)
+    del entrymap[graph.startblock]
+    builder = DataFlowFamilyBuilder(graph)
     variable_families = builder.get_variable_families()
     del builder
 
     pending = []     # list of (block, var-used-but-not-defined)
-
-    for block in entrymap:
+    for block in graph.iterblocks():
+        if block not in entrymap:
+            continue
         variables_created = variables_created_in(block)
-        variables_used = {}
+        seen = set(variables_created)
+        variables_used = []
+        def record_used_var(v):
+            if v not in seen:
+                variables_used.append(v)
+                seen.add(v)
         for op in block.operations:
-            for v in op.args:
-                variables_used[v] = True
-        variables_used[block.exitswitch] = True
+            for arg in op.args:
+                record_used_var(arg)
+        record_used_var(block.exitswitch)
         for link in block.exits:
-            for v in link.args:
-                variables_used[v] = True
+            for arg in link.args:
+                record_used_var(arg)
 
         for v in variables_used:
-            if isinstance(v, Variable):
-                if v not in variables_created:
-                    pending.append((block, v))
+            if (isinstance(v, Variable) and
+                    v._name not in ('last_exception_', 'last_exc_value_')):
+                pending.append((block, v))
 
     while pending:
         block, v = pending.pop()
@@ -190,8 +177,7 @@
         for w in variables_created:
             w_rep = variable_families.find_rep(w)
             if v_rep is w_rep:
-                # 'w' is in the same family as 'v', so we can simply
-                # reuse its value for 'v'
+                # 'w' is in the same family as 'v', so we can reuse it
                 block.renamevariables({v: w})
                 break
         else:
diff --git a/rpython/translator/backendopt/test/test_escape.py b/rpython/translator/backendopt/test/test_escape.py
--- a/rpython/translator/backendopt/test/test_escape.py
+++ b/rpython/translator/backendopt/test/test_escape.py
@@ -111,7 +111,9 @@
         a.x = 12
         return a1.x
     t, adi, graph = build_adi(fn6, [int])
-    avar = graph.startblock.exits[0].target.inputargs[1]
+    op = graph.startblock.exits[0].target.operations[0]
+    assert op.opname == 'setfield'
+    avar = op.args[0]
     state = adi.getstate(avar)
     assert len(state.creation_points) == 2
     for crep in state.creation_points:
@@ -179,7 +181,7 @@
     t, adi, graph = build_adi(f, [])
     g_graph = graphof(t, g)
     a0var = graph.startblock.operations[0].result
-    b0var = graph.startblock.operations[3].result 
+    b0var = graph.startblock.operations[3].result
     a0state = adi.getstate(a0var)
     b0state = adi.getstate(b0var)
     a0crep, = a0state.creation_points
diff --git a/rpython/translator/backendopt/test/test_ssa.py b/rpython/translator/backendopt/test/test_ssa.py
--- a/rpython/translator/backendopt/test/test_ssa.py
+++ b/rpython/translator/backendopt/test/test_ssa.py
@@ -1,7 +1,7 @@
 from rpython.translator.backendopt.ssa import *
 from rpython.translator.translator import TranslationContext
-from rpython.flowspace.model import Block, Link, Variable, Constant
-from rpython.flowspace.model import SpaceOperation
+from rpython.flowspace.model import (
+    Block, Link, Variable, Constant, SpaceOperation, FunctionGraph)
 
 
 def test_data_flow_families():
@@ -60,16 +60,14 @@
     b2 = Block([x])
     b3 = Block([])
 
+    graph = FunctionGraph('x', b1)
     b2.operations.append(SpaceOperation('add', [x, c], y))
     b2.exitswitch = y
 
     b1.closeblock(Link([Constant(0)], b2))
     b2.closeblock(Link([y], b2), Link([], b3))
-    b3.closeblock(Link([y, c], None))
-
-    SSA_to_SSI({b1: True,     # reachable from outside
-                b2: False,
-                b3: False})
+    b3.closeblock(Link([y, c], graph.exceptblock))
+    SSA_to_SSI(graph)
 
     assert len(b1.inputargs) == 1
     assert len(b2.inputargs) == 2
@@ -100,10 +98,8 @@
 
     b3.operations.append(SpaceOperation('hello', [y], z))
     b1.closeblock(Link([x], b2), Link([], b3))
-
-    SSA_to_SSI({b1: True,     # reachable from outside
-                b2: False,
-                b3: False})
+    graph = FunctionGraph('x', b1)
+    SSA_to_SSI(graph)
 
     assert b1.inputargs == [x]
     assert b2.inputargs == [y]
diff --git a/rpython/translator/simplify.py b/rpython/translator/simplify.py
--- a/rpython/translator/simplify.py
+++ b/rpython/translator/simplify.py
@@ -7,13 +7,15 @@
 import py
 from collections import defaultdict
 
+from rpython.tool.algo.unionfind import UnionFind
 from rpython.flowspace.model import (Variable, Constant,
                                      c_last_exception, checkgraph, mkentrymap)
 from rpython.flowspace.operation import OverflowingOperation, op
 from rpython.rlib import rarithmetic
 from rpython.translator import unsimplify
-from rpython.translator.backendopt import ssa
 from rpython.rtyper.lltypesystem import lloperation, lltype
+from rpython.translator.backendopt.ssa import (
+        SSA_to_SSI, DataFlowFamilyBuilder)
 
 def get_graph(arg, translator):
     if isinstance(arg, Variable):
@@ -75,10 +77,12 @@
                 outputargs = []
                 for v in exit.args:
                     if isinstance(v, Variable):
-                        # this variable is valid in the context of block1
-                        # but it must come from 'link'
-                        i = block1.inputargs.index(v)
-                        v = link.args[i]
+                        try:
+                            i = block1.inputargs.index(v)
+                            v = link.args[i]
+                        except ValueError:
+                            # the variable was passed implicitly to block1
+                            pass
                     outputargs.append(v)
                 link.args = outputargs
                 link.target = exit.target
@@ -243,6 +247,59 @@
             seen.append(case)
         block.recloseblock(*exits)
 
+def constfold_exitswitch(graph):
+    """Remove trivial links by merging their source and target blocks
+
+    A link is trivial if it has no arguments, is the single exit of its
+    source and the single parent of its target.
+    """
+    block = graph.startblock
+    seen = set([block])
+    stack = list(block.exits)
+    while stack:
+        link = stack.pop()
+        target = link.target
+        if target in seen:
+            continue
+        source = link.prevblock
+        switch = source.exitswitch
+        if (isinstance(switch, Constant) and switch != c_last_exception):
+            exits = replace_exitswitch_by_constant(source, switch)
+            stack.extend(exits)
+        else:
+            seen.add(target)
+            stack.extend(target.exits)
+
+
+def remove_trivial_links(graph):
+    """Remove trivial links by merging their source and target blocks
+
+    A link is trivial if it has no arguments, is the single exit of its
+    source and the single parent of its target.
+    """
+    entrymap = mkentrymap(graph)
+    block = graph.startblock
+    seen = set([block])
+    stack = list(block.exits)
+    while stack:
+        link = stack.pop()
+        if link.target in seen:
+            continue
+        source = link.prevblock
+        target = link.target
+        if (not link.args and source.exitswitch is None and
+                len(entrymap[target]) == 1 and
+                target.exits):  # stop at the returnblock
+            assert len(source.exits) == 1
+            source.operations.extend(target.operations)
+            source.exitswitch = newexitswitch = target.exitswitch
+            source.recloseblock(*target.exits)
+            stack.extend(source.exits)
+        else:
+            seen.add(target)
+            stack.extend(target.exits)
+
+
 def join_blocks(graph):
     """Links can be deleted if they are the single exit of a block and
     the single entry point of the next block.  When this happens, we can
@@ -504,6 +561,77 @@
             if block.inputargs[i] not in read_vars:
                 del block.inputargs[i]
 
+class Representative(object):
+    def __init__(self, var):
+        self.rep = var
+
+    def absorb(self, other):
+        pass
+
+def all_equal(lst):
+    first = lst[0]
+    return all(first == x for x in lst[1:])
+
+def isspecialvar(v):
+    return isinstance(v, Variable) and v._name in ('last_exception_', 'last_exc_value_')
+
+def remove_identical_vars_SSA(graph):
+    """When the same variable is passed multiple times into the next block,
+    pass it only once.  This enables further optimizations by the annotator,
+    which otherwise doesn't realize that tests performed on one of the copies
+    of the variable also affect the other."""
+    uf = UnionFind(Representative)
+    entrymap = mkentrymap(graph)
+    del entrymap[graph.startblock]
+    entrymap.pop(graph.returnblock, None)
+    entrymap.pop(graph.exceptblock, None)
+    inputs = {}
+    for block, links in entrymap.items():
+        phis = zip(block.inputargs, zip(*[link.args for link in links]))
+        inputs[block] = phis
+
+    def simplify_phis(block):
+        phis = inputs[block]
+        to_remove = []
+        unique_phis = {}
+        for i, (input, phi_args) in enumerate(phis):
+            new_args = [uf.find_rep(arg) for arg in phi_args]
+            if all_equal(new_args) and not isspecialvar(new_args[0]):
+                uf.union(new_args[0], input)
+                to_remove.append(i)
+            else:
+                t = tuple(new_args)
+                if t in unique_phis:
+                    uf.union(unique_phis[t], input)
+                    to_remove.append(i)
+                else:
+                    unique_phis[t] = input
+        for i in reversed(to_remove):
+            del phis[i]
+        return bool(to_remove)
+
+    progress = True
+    while progress:
+        progress = False
+        for block in inputs:
+            if simplify_phis(block):
+                progress = True
+
+    renaming = {key: uf[key].rep for key in uf}
+    for block, links in entrymap.items():
+        if inputs[block]:
+            new_inputs, new_args = zip(*inputs[block])
+            new_args = map(list, zip(*new_args))
+        else:
+            new_inputs = []
+            new_args = [[] for _ in links]
+        block.inputargs = new_inputs
+        assert len(links) == len(new_args)
+        for link, args in zip(links, new_args):
+            link.args = args
+    for block in graph.iterblocks():
+        block.renamevariables(renaming)
+
 def remove_identical_vars(graph):
     """When the same variable is passed multiple times into the next block,
     pass it only once.  This enables further optimizations by the annotator,
@@ -530,7 +658,7 @@
     #    when for all possible incoming paths they would get twice the same
     #    value (this is really the purpose of remove_identical_vars()).
     #
-    builder = ssa.DataFlowFamilyBuilder(graph)
+    builder = DataFlowFamilyBuilder(graph)
     variable_families = builder.get_variable_families()  # vertical removal
     while True:
         if not builder.merge_identical_phi_nodes():    # horizontal removal
@@ -625,7 +753,7 @@
     # NB. this assumes RPythonicity: we can only iterate over something
     # that has a len(), and this len() cannot change as long as we are
     # using the iterator.
-    builder = ssa.DataFlowFamilyBuilder(graph)
+    builder = DataFlowFamilyBuilder(graph)
     variable_families = builder.get_variable_families()
     c_append = Constant('append')
     newlist_v = {}
@@ -958,12 +1086,14 @@
 # ____ all passes & simplify_graph
 
 all_passes = [
+    transform_dead_op_vars,
     eliminate_empty_blocks,
     remove_assertion_errors,
-    join_blocks,
+    remove_identical_vars_SSA,
+    constfold_exitswitch,
+    remove_trivial_links,
+    SSA_to_SSI,
     coalesce_bool,
-    transform_dead_op_vars,
-    remove_identical_vars,
     transform_ovfcheck,
     simplify_exceptions,
     transform_xxxitem,
@@ -974,7 +1104,6 @@
     """inplace-apply all the existing optimisations to the graph."""
     if passes is True:
         passes = all_passes
-    checkgraph(graph)
     for pass_ in passes:
         pass_(graph)
     checkgraph(graph)
diff --git a/rpython/translator/tool/make_dot.py b/rpython/translator/tool/make_dot.py
--- a/rpython/translator/tool/make_dot.py
+++ b/rpython/translator/tool/make_dot.py
@@ -1,7 +1,6 @@
 import os
 import inspect, linecache
 from rpython.flowspace.model import *
-from rpython.flowspace.objspace import build_flow
 from rpython.tool.udir import udir
 from py.process import cmdexec
 from rpython.tool.error import offset2lineno
@@ -238,14 +237,3 @@
     # not a keyword
     name = ''.join([CHAR_MAP[c] for c in name])
     return '_' + name
-
-
-if __name__ == '__main__':
-    def f(x):
-        i = 0
-        while i < x:
-            i += 1
-        return i
-
-    graph = build_flow(f)
-    make_dot('f', graph)


More information about the pypy-commit mailing list