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