Author: Devin Jeanpierre <[email protected]>
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
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit