Author: Devin Jeanpierre <jeanpierr...@gmail.com>
Branch: inline-blocks
Changeset: r85838:85fa82cf9d39
Date: 2016-07-24 13:25 +0200
http://bitbucket.org/pypy/pypy/changeset/85fa82cf9d39/

Log:    Inline gotos to blocks with only one predecessor.

        This is a small subset of the patch in issue #1220.

        Example output:

        Signed pypy_g_main(Signed l_x_0) { Signed l_v131;

         /* block0: (inlined) */ OP_INT_ADD(l_x_0, 1L, l_v131); /* block1:
        (inlined) */ RPY_DEBUG_RETURN(); return l_v131; }

        Instead of something like:

        Signed pypy_g_main(Signed l_x_0) { Signed l_v131; goto block0;

         block0: OP_INT_ADD(l_x_0, 1L, l_v131); goto block1;

         block1: RPY_DEBUG_RETURN(); return l_v131; }

        The readability benefit is when you have if statements and such.

        This does NOT handle a case like:

        if (x) { <...>; goto block1; } else { <...>; goto block1; } block1:
        ...

        That case is a bit more fragile and maybe not worth the cost of
        implementing.

diff --git a/rpython/translator/c/funcgen.py b/rpython/translator/c/funcgen.py
--- a/rpython/translator/c/funcgen.py
+++ b/rpython/translator/c/funcgen.py
@@ -2,7 +2,7 @@
 from rpython.translator.c.support import cdecl
 from rpython.translator.c.support import llvalue_from_constant, gen_assignments
 from rpython.translator.c.support import c_string_constant, barebonearray
-from rpython.flowspace.model import Variable, Constant
+from rpython.flowspace.model import Variable, Constant, mkentrymap
 from rpython.rtyper.lltypesystem.lltype import (Ptr, Void, Bool, Signed, 
Unsigned,
     SignedLongLong, Float, UnsignedLongLong, Char, UniChar, ContainerType,
     Array, FixedSizeArray, ForwardReference, FuncType)
@@ -173,76 +173,95 @@
 
     def cfunction_body(self):
         graph = self.graph
-        yield 'goto block0;'    # to avoid a warning "this label is not used"
+        # yield 'goto block0;'    # to avoid a warning "this label is not used"
 
-        # generate the body of each block
+        # Locate blocks with a single predecessor, which can be written
+        # inline in place of a "goto":
+        entrymap = mkentrymap(graph)
+        self.inlinable_blocks = {
+            block for block in entrymap if len(entrymap[block]) == 1}
+
+        yield ''
+        for line in self.gen_goto(graph.startblock):
+            yield line
+
+        # Only blocks left are those that have more than one predecessor.
         for block in graph.iterblocks():
-            myblocknum = self.blocknum[block]
-            yield ''
+            if block in self.inlinable_blocks:
+                continue
+            for line in self.gen_block(block):
+                yield line
+
+    def gen_block(self, block):
+        myblocknum = self.blocknum[block]
+        if block in self.inlinable_blocks:
+            # debug comment
+            yield '/* block%d: (inlined) */' % myblocknum
+        else:
             yield 'block%d:' % myblocknum
-            if block in self.innerloops:
-                for line in self.gen_while_loop_hack(block):
-                    yield line
-                continue
-            for i, op in enumerate(block.operations):
-                for line in self.gen_op(op):
-                    yield line
-            if len(block.exits) == 0:
-                assert len(block.inputargs) == 1
-                # regular return block
-                retval = self.expr(block.inputargs[0])
-                if self.exception_policy != "exc_helper":
-                    yield 'RPY_DEBUG_RETURN();'
-                yield 'return %s;' % retval
-                continue
-            elif block.exitswitch is None:
-                # single-exit block
-                assert len(block.exits) == 1
-                for op in self.gen_link(block.exits[0]):
+        if block in self.innerloops:
+            for line in self.gen_while_loop_hack(block):
+                yield line
+            return
+        for i, op in enumerate(block.operations):
+            for line in self.gen_op(op):
+                yield line
+        if len(block.exits) == 0:
+            assert len(block.inputargs) == 1
+            # regular return block
+            retval = self.expr(block.inputargs[0])
+            if self.exception_policy != "exc_helper":
+                yield 'RPY_DEBUG_RETURN();'
+            yield 'return %s;' % retval
+            return
+        elif block.exitswitch is None:
+            # single-exit block
+            assert len(block.exits) == 1
+            for op in self.gen_link(block.exits[0]):
+                yield op
+        else:
+            assert not block.canraise
+            # block ending in a switch on a value
+            TYPE = self.lltypemap(block.exitswitch)
+            if TYPE == Bool:
+                expr = self.expr(block.exitswitch)
+                for link in block.exits[:0:-1]:
+                    assert link.exitcase in (False, True)
+                    if not link.exitcase:
+                        expr = '!' + expr
+                    yield 'if (%s) {' % expr
+                    for op in self.gen_link(link):
+                        yield '\t' + op
+                    yield '}'
+                link = block.exits[0]
+                assert link.exitcase in (False, True)
+                for op in self.gen_link(link):
                     yield op
+            elif TYPE in (Signed, Unsigned, SignedLongLong,
+                            UnsignedLongLong, Char, UniChar):
+                defaultlink = None
+                expr = self.expr(block.exitswitch)
+                yield 'switch (%s) {' % self.expr(block.exitswitch)
+                for link in block.exits:
+                    if link.exitcase == 'default':
+                        defaultlink = link
+                        continue
+                    yield 'case %s:' % self.db.get(link.llexitcase)
+                    for op in self.gen_link(link):
+                        yield '\t' + op
+                    # 'break;' not needed, as gen_link ends in a 'goto'
+                # Emit default case
+                yield 'default:'
+                if defaultlink is None:
+                    yield '\tassert(!"bad switch!!"); abort();'
+                else:
+                    for op in self.gen_link(defaultlink):
+                        yield '\t' + op
+
+                yield '}'
             else:
-                assert not block.canraise
-                # block ending in a switch on a value
-                TYPE = self.lltypemap(block.exitswitch)
-                if TYPE == Bool:
-                    expr = self.expr(block.exitswitch)
-                    for link in block.exits[:0:-1]:
-                        assert link.exitcase in (False, True)
-                        if not link.exitcase:
-                            expr = '!' + expr
-                        yield 'if (%s) {' % expr
-                        for op in self.gen_link(link):
-                            yield '\t' + op
-                        yield '}'
-                    link = block.exits[0]
-                    assert link.exitcase in (False, True)
-                    for op in self.gen_link(link):
-                        yield op
-                elif TYPE in (Signed, Unsigned, SignedLongLong,
-                              UnsignedLongLong, Char, UniChar):
-                    defaultlink = None
-                    expr = self.expr(block.exitswitch)
-                    yield 'switch (%s) {' % self.expr(block.exitswitch)
-                    for link in block.exits:
-                        if link.exitcase == 'default':
-                            defaultlink = link
-                            continue
-                        yield 'case %s:' % self.db.get(link.llexitcase)
-                        for op in self.gen_link(link):
-                            yield '\t' + op
-                        # 'break;' not needed, as gen_link ends in a 'goto'
-                    # Emit default case
-                    yield 'default:'
-                    if defaultlink is None:
-                        yield '\tassert(!"bad switch!!"); abort();'
-                    else:
-                        for op in self.gen_link(defaultlink):
-                            yield '\t' + op
-
-                    yield '}'
-                else:
-                    raise TypeError("exitswitch type not supported"
-                                    "  Got %r" % (TYPE,))
+                raise TypeError("exitswitch type not supported"
+                                "  Got %r" % (TYPE,))
 
     def gen_link(self, link):
         "Generate the code to jump across the given Link."
@@ -256,12 +275,25 @@
             assignments.append((a2typename, dest, src))
         for line in gen_assignments(assignments):
             yield line
-        label = 'block%d' % self.blocknum[link.target]
-        if link.target in self.innerloops:
-            loop = self.innerloops[link.target]
+        for line in self.gen_goto(link.target, link):
+            yield line
+
+    def gen_goto(self, target, link=None):
+        """Recursively expand block with inlining or goto.
+
+        Blocks that have only one predecessor are inlined directly, all others
+        are reached via goto.
+        """
+        label = 'block%d' % self.blocknum[target]
+        if target in self.innerloops:
+            loop = self.innerloops[target]
             if link is loop.links[-1]:   # link that ends a loop
                 label += '_back'
-        yield 'goto %s;' % label
+        if target in self.inlinable_blocks:
+            for line in self.gen_block(target):
+                yield line
+        else:
+            yield 'goto %s;' % label
 
     def gen_op(self, op):
         macro = 'OP_%s' % op.opname.upper()
diff --git a/rpython/translator/c/test/test_genc.py 
b/rpython/translator/c/test/test_genc.py
--- a/rpython/translator/c/test/test_genc.py
+++ b/rpython/translator/c/test/test_genc.py
@@ -1,4 +1,5 @@
 import ctypes
+import re
 from collections import OrderedDict
 
 import py
@@ -13,6 +14,7 @@
 from rpython.rtyper.lltypesystem.rstr import STR
 from rpython.tool.nullpath import NullPyPathLocal
 from rpython.translator.c import genc
+from rpython.translator.backendopt.merge_if_blocks import merge_if_blocks
 from rpython.translator.interactive import Translation
 from rpython.translator.translator import TranslationContext, graphof
 
@@ -604,3 +606,23 @@
     else:
         assert 0, "the call was not found in the C source"
     assert 'PYPY_INHIBIT_TAIL_CALL();' in lines[i+1]
+
+def get_generated_c_source(fn, types):
+    """Return the generated C source for fn."""
+    t = Translation(fn, types, backend="c")
+    t.annotate()
+    merge_if_blocks(t.driver.translator.graphs[0])
+    c_filename_path = t.source_c()
+    return t.driver.cbuilder.c_source_filename.join('..',
+                              'rpython_translator_c_test.c').read()
+
+def test_generated_c_source_no_gotos():
+    # We want simple functions to have no indirection/goto.
+    # Instead, PyPy can inline blocks when they aren't reused.
+
+    def main(x):
+        return x + 1
+
+    c_src = get_generated_c_source(main, [int])
+    assert 'goto' not in c_src
+    assert not re.search(r'block\w*:(?! \(inlined\))', c_src)
_______________________________________________
pypy-commit mailing list
pypy-commit@python.org
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to