[pypy-commit] pypy default: Merged in inline-blocks (pull request #467)

arigo pypy.commits at gmail.com
Tue Aug 9 05:16:23 EDT 2016


Author: Armin Rigo <armin.rigo at gmail.com>
Branch: 
Changeset: r86100:4c64ef74c612
Date: 2016-08-09 11:14 +0200
http://bitbucket.org/pypy/pypy/changeset/4c64ef74c612/

Log:	Merged in inline-blocks (pull request #467)

	Inline gotos to blocks with only one predecessor.

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,17 +173,37 @@
 
     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():
+            if block in self.inlinable_blocks:
+                continue
+            for line in self.gen_block(block):
+                yield line
+
+    def gen_block(self, block):
+        if 1:      # (preserve indentation)
             myblocknum = self.blocknum[block]
-            yield ''
-            yield 'block%d:' % myblocknum
+            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
+                return
             for i, op in enumerate(block.operations):
                 for line in self.gen_op(op):
                     yield line
@@ -194,7 +214,7 @@
                 if self.exception_policy != "exc_helper":
                     yield 'RPY_DEBUG_RETURN();'
                 yield 'return %s;' % retval
-                continue
+                return
             elif block.exitswitch is None:
                 # single-exit block
                 assert len(block.exits) == 1
@@ -256,12 +276,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)


More information about the pypy-commit mailing list