Commits

Ronan Lamy committed 5838c81

Move pypy.interpreter.generator to flow space

Comments (0)

Files changed (5)

pypy/objspace/flow/generator.py

+from pypy.objspace.flow.model import Block, Link, SpaceOperation, checkgraph
+from pypy.objspace.flow.model import Variable, Constant, FunctionGraph
+from pypy.translator.unsimplify import insert_empty_startblock
+from pypy.translator.unsimplify import split_block
+from pypy.translator.simplify import eliminate_empty_blocks, simplify_graph
+from pypy.tool.sourcetools import func_with_new_name
+from pypy.interpreter.argument import Signature
+
+
+class AbstractPosition(object):
+    _immutable_ = True
+    _attrs_ = ()
+
+
+def tweak_generator_graph(graph):
+    if not hasattr(graph.func, '_generator_next_method_of_'):
+        # This is the first copy of the graph.  We replace it with
+        # a small bootstrap graph.
+        GeneratorIterator = make_generatoriterator_class(graph)
+        replace_graph_with_bootstrap(GeneratorIterator, graph)
+        # We attach a 'next' method to the GeneratorIterator class
+        # that will invoke the real function, based on a second
+        # copy of the graph.
+        attach_next_method(GeneratorIterator, graph)
+    else:
+        # This is the second copy of the graph.  Tweak it.
+        GeneratorIterator = graph.func._generator_next_method_of_
+        tweak_generator_body_graph(GeneratorIterator.Entry, graph)
+
+
+def make_generatoriterator_class(graph):
+    class GeneratorIterator(object):
+        class Entry(AbstractPosition):
+            _immutable_ = True
+            varnames = get_variable_names(graph.startblock.inputargs)
+
+        def __init__(self, entry):
+            self.current = entry
+
+        def __iter__(self):
+            return self
+
+    return GeneratorIterator
+
+def replace_graph_with_bootstrap(GeneratorIterator, graph):
+    Entry = GeneratorIterator.Entry
+    newblock = Block(graph.startblock.inputargs)
+    v_generator = Variable('generator')
+    v_entry = Variable('entry')
+    newblock.operations.append(
+        SpaceOperation('simple_call', [Constant(Entry)], v_entry))
+    assert len(graph.startblock.inputargs) == len(Entry.varnames)
+    for v, name in zip(graph.startblock.inputargs, Entry.varnames):
+        newblock.operations.append(
+            SpaceOperation('setattr', [v_entry, Constant(name), v],
+                           Variable()))
+    newblock.operations.append(
+        SpaceOperation('simple_call', [Constant(GeneratorIterator), v_entry],
+                       v_generator))
+    newblock.closeblock(Link([v_generator], graph.returnblock))
+    graph.startblock = newblock
+
+def attach_next_method(GeneratorIterator, graph):
+    func = graph.func
+    func = func_with_new_name(func, '%s__next' % (func.func_name,))
+    func._generator_next_method_of_ = GeneratorIterator
+    func._always_inline_ = True
+    #
+    def next(self):
+        entry = self.current
+        self.current = None
+        assert entry is not None      # else, recursive generator invocation
+        (next_entry, return_value) = func(entry)
+        self.current = next_entry
+        return return_value
+    GeneratorIterator.next = next
+    return func   # for debugging
+
+def get_variable_names(variables):
+    seen = set()
+    result = []
+    for v in variables:
+        name = v._name.strip('_')
+        while name in seen:
+            name += '_'
+        result.append('g_' + name)
+        seen.add(name)
+    return result
+
+def _insert_reads(block, varnames):
+    assert len(varnames) == len(block.inputargs)
+    v_entry1 = Variable('entry')
+    for i, name in enumerate(varnames):
+        block.operations.insert(i,
+            SpaceOperation('getattr', [v_entry1, Constant(name)],
+                           block.inputargs[i]))
+    block.inputargs = [v_entry1]
+
+def tweak_generator_body_graph(Entry, graph):
+    # First, always run simplify_graph in order to reduce the number of
+    # variables passed around
+    simplify_graph(graph)
+    #
+    assert graph.startblock.operations[0].opname == 'generator_mark'
+    graph.startblock.operations.pop(0)
+    #
+    insert_empty_startblock(None, graph)
+    _insert_reads(graph.startblock, Entry.varnames)
+    Entry.block = graph.startblock
+    #
+    mappings = [Entry]
+    #
+    stopblock = Block([])
+    v0 = Variable(); v1 = Variable()
+    stopblock.operations = [
+        SpaceOperation('simple_call', [Constant(StopIteration)], v0),
+        SpaceOperation('type', [v0], v1),
+        ]
+    stopblock.closeblock(Link([v1, v0], graph.exceptblock))
+    #
+    for block in list(graph.iterblocks()):
+        for exit in block.exits:
+            if exit.target is graph.returnblock:
+                exit.args = []
+                exit.target = stopblock
+        assert block is not stopblock
+        for index in range(len(block.operations)-1, -1, -1):
+            op = block.operations[index]
+            if op.opname == 'yield':
+                [v_yielded_value] = op.args
+                del block.operations[index]
+                newlink = split_block(None, block, index)
+                newblock = newlink.target
+                #
+                class Resume(AbstractPosition):
+                    _immutable_ = True
+                    block = newblock
+                Resume.__name__ = 'Resume%d' % len(mappings)
+                mappings.append(Resume)
+                varnames = get_variable_names(newlink.args)
+                #
+                _insert_reads(newblock, varnames)
+                #
+                v_resume = Variable('resume')
+                block.operations.append(
+                    SpaceOperation('simple_call', [Constant(Resume)],
+                                   v_resume))
+                for i, name in enumerate(varnames):
+                    block.operations.append(
+                        SpaceOperation('setattr', [v_resume, Constant(name),
+                                                   newlink.args[i]],
+                                       Variable()))
+                v_pair = Variable('pair')
+                block.operations.append(
+                    SpaceOperation('newtuple', [v_resume, v_yielded_value],
+                                   v_pair))
+                newlink.args = [v_pair]
+                newlink.target = graph.returnblock
+    #
+    regular_entry_block = Block([Variable('entry')])
+    block = regular_entry_block
+    for Resume in mappings:
+        v_check = Variable()
+        block.operations.append(
+            SpaceOperation('simple_call', [Constant(isinstance),
+                                           block.inputargs[0],
+                                           Constant(Resume)],
+                           v_check))
+        block.exitswitch = v_check
+        link1 = Link([block.inputargs[0]], Resume.block)
+        link1.exitcase = True
+        nextblock = Block([Variable('entry')])
+        link2 = Link([block.inputargs[0]], nextblock)
+        link2.exitcase = False
+        block.closeblock(link1, link2)
+        block = nextblock
+    block.closeblock(Link([Constant(AssertionError),
+                           Constant(AssertionError("bad generator class"))],
+                          graph.exceptblock))
+    graph.startblock = regular_entry_block
+    graph.signature = Signature(['entry'])
+    graph.defaults = ()
+    checkgraph(graph)
+    eliminate_empty_blocks(graph)

pypy/objspace/flow/objspace.py

 from pypy.objspace.flow import operation
 from pypy.objspace.flow.flowcontext import (FlowSpaceFrame, fixeggblocks,
     FSException, FlowingError)
+from pypy.objspace.flow.generator import tweak_generator_graph
 from pypy.objspace.flow.pygraph import PyGraph
 from pypy.objspace.flow.specialcase import SPECIAL_CASES
 from pypy.rlib.unroll import unrolling_iterable, _unroller
         fixeggblocks(graph)
         checkgraph(graph)
         if graph.is_generator and tweak_for_generator:
-            from pypy.translator.generator import tweak_generator_graph
             tweak_generator_graph(graph)
         return graph
 

pypy/objspace/flow/test/test_generator.py

-from pypy.objspace.flow.test.test_objspace import Base
+from pypy.conftest import option
+from pypy.objspace.flow.objspace import FlowObjSpace
+from pypy.objspace.flow.model import Variable
+from pypy.interpreter.argument import Signature
+from pypy.objspace.flow.generator import (make_generatoriterator_class,
+        replace_graph_with_bootstrap, get_variable_names,
+        tweak_generator_body_graph, attach_next_method)
+from pypy.translator.simplify import join_blocks
 
 
-class TestGenerator(Base):
+# ____________________________________________________________
 
-    def test_simple_generator(self):
-        def f(n):
+def f_gen(n):
+    i = 0
+    while i < n:
+        yield i
+        i += 1
+
+class GeneratorIterator(object):
+    def __init__(self, entry):
+        self.current = entry
+    def next(self):
+        e = self.current
+        self.current = None
+        if isinstance(e, Yield1):
+            n = e.n_0
+            i = e.i_0
+            i += 1
+        else:
+            n = e.n_0
             i = 0
-            while i < n:
-                yield i
-                yield i
-                i += 1
-        graph = self.codetest(f, tweak_for_generator=False)
-        ops = self.all_operations(graph)
-        assert ops == {'generator_mark': 1,
-                       'lt': 1, 'is_true': 1,
-                       'yield': 2,
-                       'inplace_add': 1}
+        if i < n:
+            e = Yield1()
+            e.n_0 = n
+            e.i_0 = i
+            self.current = e
+            return i
+        raise StopIteration
+
+    def __iter__(self):
+        return self
+
+class AbstractPosition(object):
+    _immutable_ = True
+class Entry1(AbstractPosition):
+    _immutable_ = True
+class Yield1(AbstractPosition):
+    _immutable_ = True
+
+def f_explicit(n):
+    e = Entry1()
+    e.n_0 = n
+    return GeneratorIterator(e)
+
+def test_explicit():
+    assert list(f_gen(10)) == list(f_explicit(10))
+
+def test_get_variable_names():
+    lst = get_variable_names([Variable('a'), Variable('b_'), Variable('a')])
+    assert lst == ['g_a', 'g_b', 'g_a_']
+
+# ____________________________________________________________
+
+
+class TestGenerator:
+
+    def test_replace_graph_with_bootstrap(self):
+        def func(n, x, y, z):
+            yield n
+            yield n
+        #
+        space = FlowObjSpace()
+        graph = space.build_flow(func, tweak_for_generator=False)
+        assert graph.startblock.operations[0].opname == 'generator_mark'
+        GeneratorIterator = make_generatoriterator_class(graph)
+        replace_graph_with_bootstrap(GeneratorIterator, graph)
+        if option.view:
+            graph.show()
+        block = graph.startblock
+        ops = block.operations
+        assert ops[0].opname == 'simple_call' # e = Entry1()
+        assert ops[1].opname == 'setattr'     # e.g_n = n
+        assert ops[1].args[1].value == 'g_n'
+        assert ops[2].opname == 'setattr'     # e.g_x = x
+        assert ops[2].args[1].value == 'g_x'
+        assert ops[3].opname == 'setattr'     # e.g_y = y
+        assert ops[3].args[1].value == 'g_y'
+        assert ops[4].opname == 'setattr'     # e.g_z = z
+        assert ops[4].args[1].value == 'g_z'
+        assert ops[5].opname == 'simple_call' # g = GeneratorIterator(e)
+        assert ops[5].args[1] == ops[0].result
+        assert len(ops) == 6
+        assert len(block.exits) == 1
+        assert block.exits[0].target is graph.returnblock
+
+    def test_tweak_generator_body_graph(self):
+        def f(n, x, y, z=3):
+            z *= 10
+            yield n + 1
+            z -= 10
+        #
+        space = FlowObjSpace()
+        graph = space.build_flow(f, tweak_for_generator=False)
+        class Entry:
+            varnames = ['g_n', 'g_x', 'g_y', 'g_z']
+        tweak_generator_body_graph(Entry, graph)
+        if option.view:
+            graph.show()
+        # XXX how to test directly that the graph is correct?  :-(
+        assert len(graph.startblock.inputargs) == 1
+        assert graph.signature == Signature(['entry'])
+        assert graph.defaults == ()
+
+    def test_tweak_generator_graph(self):
+        def f(n, x, y, z):
+            z *= 10
+            yield n + 1
+            z -= 10
+        #
+        space = FlowObjSpace()
+        graph = space.build_flow(f, tweak_for_generator=False)
+        GeneratorIterator = make_generatoriterator_class(graph)
+        replace_graph_with_bootstrap(GeneratorIterator, graph)
+        func1 = attach_next_method(GeneratorIterator, graph)
+        if option.view:
+            graph.show()
+        #
+        assert func1._generator_next_method_of_ is GeneratorIterator
+        assert hasattr(GeneratorIterator, 'next')
+        #
+        graph_next = space.build_flow(GeneratorIterator.next.im_func)
+        join_blocks(graph_next)
+        if option.view:
+            graph_next.show()
+        #
+        graph1 = space.build_flow(func1, tweak_for_generator=False)
+        tweak_generator_body_graph(GeneratorIterator.Entry, graph1)
+        if option.view:
+            graph1.show()
+
+    def test_automatic(self):
+        def f(n, x, y, z):
+            z *= 10
+            yield n + 1
+            z -= 10
+        #
+        space = FlowObjSpace()
+        graph = space.build_flow(f)   # tweak_for_generator=True
+        if option.view:
+            graph.show()
+        block = graph.startblock
+        assert len(block.exits) == 1
+        assert block.exits[0].target is graph.returnblock

pypy/translator/generator.py

-from pypy.objspace.flow.model import Block, Link, SpaceOperation, checkgraph
-from pypy.objspace.flow.model import Variable, Constant, FunctionGraph
-from pypy.translator.unsimplify import insert_empty_startblock
-from pypy.translator.unsimplify import split_block
-from pypy.translator.simplify import eliminate_empty_blocks, simplify_graph
-from pypy.tool.sourcetools import func_with_new_name
-from pypy.interpreter.argument import Signature
-
-
-class AbstractPosition(object):
-    _immutable_ = True
-    _attrs_ = ()
-
-
-def tweak_generator_graph(graph):
-    if not hasattr(graph.func, '_generator_next_method_of_'):
-        # This is the first copy of the graph.  We replace it with
-        # a small bootstrap graph.
-        GeneratorIterator = make_generatoriterator_class(graph)
-        replace_graph_with_bootstrap(GeneratorIterator, graph)
-        # We attach a 'next' method to the GeneratorIterator class
-        # that will invoke the real function, based on a second
-        # copy of the graph.
-        attach_next_method(GeneratorIterator, graph)
-    else:
-        # This is the second copy of the graph.  Tweak it.
-        GeneratorIterator = graph.func._generator_next_method_of_
-        tweak_generator_body_graph(GeneratorIterator.Entry, graph)
-
-
-def make_generatoriterator_class(graph):
-    class GeneratorIterator(object):
-        class Entry(AbstractPosition):
-            _immutable_ = True
-            varnames = get_variable_names(graph.startblock.inputargs)
-
-        def __init__(self, entry):
-            self.current = entry
-
-        def __iter__(self):
-            return self
-
-    return GeneratorIterator
-
-def replace_graph_with_bootstrap(GeneratorIterator, graph):
-    Entry = GeneratorIterator.Entry
-    newblock = Block(graph.startblock.inputargs)
-    v_generator = Variable('generator')
-    v_entry = Variable('entry')
-    newblock.operations.append(
-        SpaceOperation('simple_call', [Constant(Entry)], v_entry))
-    assert len(graph.startblock.inputargs) == len(Entry.varnames)
-    for v, name in zip(graph.startblock.inputargs, Entry.varnames):
-        newblock.operations.append(
-            SpaceOperation('setattr', [v_entry, Constant(name), v],
-                           Variable()))
-    newblock.operations.append(
-        SpaceOperation('simple_call', [Constant(GeneratorIterator), v_entry],
-                       v_generator))
-    newblock.closeblock(Link([v_generator], graph.returnblock))
-    graph.startblock = newblock
-
-def attach_next_method(GeneratorIterator, graph):
-    func = graph.func
-    func = func_with_new_name(func, '%s__next' % (func.func_name,))
-    func._generator_next_method_of_ = GeneratorIterator
-    func._always_inline_ = True
-    #
-    def next(self):
-        entry = self.current
-        self.current = None
-        assert entry is not None      # else, recursive generator invocation
-        (next_entry, return_value) = func(entry)
-        self.current = next_entry
-        return return_value
-    GeneratorIterator.next = next
-    return func   # for debugging
-
-def get_variable_names(variables):
-    seen = set()
-    result = []
-    for v in variables:
-        name = v._name.strip('_')
-        while name in seen:
-            name += '_'
-        result.append('g_' + name)
-        seen.add(name)
-    return result
-
-def _insert_reads(block, varnames):
-    assert len(varnames) == len(block.inputargs)
-    v_entry1 = Variable('entry')
-    for i, name in enumerate(varnames):
-        block.operations.insert(i,
-            SpaceOperation('getattr', [v_entry1, Constant(name)],
-                           block.inputargs[i]))
-    block.inputargs = [v_entry1]
-
-def tweak_generator_body_graph(Entry, graph):
-    # First, always run simplify_graph in order to reduce the number of
-    # variables passed around
-    simplify_graph(graph)
-    #
-    assert graph.startblock.operations[0].opname == 'generator_mark'
-    graph.startblock.operations.pop(0)
-    #
-    insert_empty_startblock(None, graph)
-    _insert_reads(graph.startblock, Entry.varnames)
-    Entry.block = graph.startblock
-    #
-    mappings = [Entry]
-    #
-    stopblock = Block([])
-    v0 = Variable(); v1 = Variable()
-    stopblock.operations = [
-        SpaceOperation('simple_call', [Constant(StopIteration)], v0),
-        SpaceOperation('type', [v0], v1),
-        ]
-    stopblock.closeblock(Link([v1, v0], graph.exceptblock))
-    #
-    for block in list(graph.iterblocks()):
-        for exit in block.exits:
-            if exit.target is graph.returnblock:
-                exit.args = []
-                exit.target = stopblock
-        assert block is not stopblock
-        for index in range(len(block.operations)-1, -1, -1):
-            op = block.operations[index]
-            if op.opname == 'yield':
-                [v_yielded_value] = op.args
-                del block.operations[index]
-                newlink = split_block(None, block, index)
-                newblock = newlink.target
-                #
-                class Resume(AbstractPosition):
-                    _immutable_ = True
-                    block = newblock
-                Resume.__name__ = 'Resume%d' % len(mappings)
-                mappings.append(Resume)
-                varnames = get_variable_names(newlink.args)
-                #
-                _insert_reads(newblock, varnames)
-                #
-                v_resume = Variable('resume')
-                block.operations.append(
-                    SpaceOperation('simple_call', [Constant(Resume)],
-                                   v_resume))
-                for i, name in enumerate(varnames):
-                    block.operations.append(
-                        SpaceOperation('setattr', [v_resume, Constant(name),
-                                                   newlink.args[i]],
-                                       Variable()))
-                v_pair = Variable('pair')
-                block.operations.append(
-                    SpaceOperation('newtuple', [v_resume, v_yielded_value],
-                                   v_pair))
-                newlink.args = [v_pair]
-                newlink.target = graph.returnblock
-    #
-    regular_entry_block = Block([Variable('entry')])
-    block = regular_entry_block
-    for Resume in mappings:
-        v_check = Variable()
-        block.operations.append(
-            SpaceOperation('simple_call', [Constant(isinstance),
-                                           block.inputargs[0],
-                                           Constant(Resume)],
-                           v_check))
-        block.exitswitch = v_check
-        link1 = Link([block.inputargs[0]], Resume.block)
-        link1.exitcase = True
-        nextblock = Block([Variable('entry')])
-        link2 = Link([block.inputargs[0]], nextblock)
-        link2.exitcase = False
-        block.closeblock(link1, link2)
-        block = nextblock
-    block.closeblock(Link([Constant(AssertionError),
-                           Constant(AssertionError("bad generator class"))],
-                          graph.exceptblock))
-    graph.startblock = regular_entry_block
-    graph.signature = Signature(['entry'])
-    graph.defaults = ()
-    checkgraph(graph)
-    eliminate_empty_blocks(graph)

pypy/translator/test/test_generator.py

-from pypy.conftest import option
-from pypy.objspace.flow.objspace import FlowObjSpace
-from pypy.objspace.flow.model import Variable
-from pypy.interpreter.argument import Signature
-from pypy.translator.translator import TranslationContext
-from pypy.translator.generator import make_generatoriterator_class
-from pypy.translator.generator import replace_graph_with_bootstrap
-from pypy.translator.generator import get_variable_names
-from pypy.translator.generator import tweak_generator_body_graph
-from pypy.translator.generator import attach_next_method
-from pypy.translator.simplify import join_blocks
-
-
-# ____________________________________________________________
-
-def f_gen(n):
-    i = 0
-    while i < n:
-        yield i
-        i += 1
-
-class GeneratorIterator(object):
-    def __init__(self, entry):
-        self.current = entry
-    def next(self):
-        e = self.current
-        self.current = None
-        if isinstance(e, Yield1):
-            n = e.n_0
-            i = e.i_0
-            i += 1
-        else:
-            n = e.n_0
-            i = 0
-        if i < n:
-            e = Yield1()
-            e.n_0 = n
-            e.i_0 = i
-            self.current = e
-            return i
-        raise StopIteration
-
-    def __iter__(self):
-        return self
-
-class AbstractPosition(object):
-    _immutable_ = True
-class Entry1(AbstractPosition):
-    _immutable_ = True
-class Yield1(AbstractPosition):
-    _immutable_ = True
-
-def f_explicit(n):
-    e = Entry1()
-    e.n_0 = n
-    return GeneratorIterator(e)
-
-def test_explicit():
-    assert list(f_gen(10)) == list(f_explicit(10))
-
-def test_get_variable_names():
-    lst = get_variable_names([Variable('a'), Variable('b_'), Variable('a')])
-    assert lst == ['g_a', 'g_b', 'g_a_']
-
-# ____________________________________________________________
-
-
-class TestGenerator:
-
-    def test_replace_graph_with_bootstrap(self):
-        def func(n, x, y, z):
-            yield n
-            yield n
-        #
-        space = FlowObjSpace()
-        graph = space.build_flow(func, tweak_for_generator=False)
-        assert graph.startblock.operations[0].opname == 'generator_mark'
-        GeneratorIterator = make_generatoriterator_class(graph)
-        replace_graph_with_bootstrap(GeneratorIterator, graph)
-        if option.view:
-            graph.show()
-        block = graph.startblock
-        ops = block.operations
-        assert ops[0].opname == 'simple_call' # e = Entry1()
-        assert ops[1].opname == 'setattr'     # e.g_n = n
-        assert ops[1].args[1].value == 'g_n'
-        assert ops[2].opname == 'setattr'     # e.g_x = x
-        assert ops[2].args[1].value == 'g_x'
-        assert ops[3].opname == 'setattr'     # e.g_y = y
-        assert ops[3].args[1].value == 'g_y'
-        assert ops[4].opname == 'setattr'     # e.g_z = z
-        assert ops[4].args[1].value == 'g_z'
-        assert ops[5].opname == 'simple_call' # g = GeneratorIterator(e)
-        assert ops[5].args[1] == ops[0].result
-        assert len(ops) == 6
-        assert len(block.exits) == 1
-        assert block.exits[0].target is graph.returnblock
-
-    def test_tweak_generator_body_graph(self):
-        def f(n, x, y, z=3):
-            z *= 10
-            yield n + 1
-            z -= 10
-        #
-        space = FlowObjSpace()
-        graph = space.build_flow(f, tweak_for_generator=False)
-        class Entry:
-            varnames = ['g_n', 'g_x', 'g_y', 'g_z']
-        tweak_generator_body_graph(Entry, graph)
-        if option.view:
-            graph.show()
-        # XXX how to test directly that the graph is correct?  :-(
-        assert len(graph.startblock.inputargs) == 1
-        assert graph.signature == Signature(['entry'])
-        assert graph.defaults == ()
-
-    def test_tweak_generator_graph(self):
-        def f(n, x, y, z):
-            z *= 10
-            yield n + 1
-            z -= 10
-        #
-        space = FlowObjSpace()
-        graph = space.build_flow(f, tweak_for_generator=False)
-        GeneratorIterator = make_generatoriterator_class(graph)
-        replace_graph_with_bootstrap(GeneratorIterator, graph)
-        func1 = attach_next_method(GeneratorIterator, graph)
-        if option.view:
-            graph.show()
-        #
-        assert func1._generator_next_method_of_ is GeneratorIterator
-        assert hasattr(GeneratorIterator, 'next')
-        #
-        graph_next = space.build_flow(GeneratorIterator.next.im_func)
-        join_blocks(graph_next)
-        if option.view:
-            graph_next.show()
-        #
-        graph1 = space.build_flow(func1, tweak_for_generator=False)
-        tweak_generator_body_graph(GeneratorIterator.Entry, graph1)
-        if option.view:
-            graph1.show()
-
-    def test_automatic(self):
-        def f(n, x, y, z):
-            z *= 10
-            yield n + 1
-            z -= 10
-        #
-        space = FlowObjSpace()
-        graph = space.build_flow(f)   # tweak_for_generator=True
-        if option.view:
-            graph.show()
-        block = graph.startblock
-        assert len(block.exits) == 1
-        assert block.exits[0].target is graph.returnblock