PageRenderTime 3ms CodeModel.GetById 19ms app.highlight 108ms RepoModel.GetById 13ms app.codeStats 1ms

/pypy/interpreter/generator.py

https://bitbucket.org/pypy/pypy/
Python | 263 lines | 209 code | 28 blank | 26 comment | 38 complexity | cd6d76b8f2b64443c9ab6e87599d33c9 MD5 | raw file
  1from pypy.interpreter.baseobjspace import W_Root
  2from pypy.interpreter.error import OperationError, oefmt
  3from pypy.interpreter.pyopcode import LoopBlock
  4from pypy.interpreter.pycode import CO_YIELD_INSIDE_TRY
  5from rpython.rlib import jit
  6
  7
  8class GeneratorIterator(W_Root):
  9    "An iterator created by a generator."
 10    _immutable_fields_ = ['pycode']
 11
 12    def __init__(self, frame):
 13        self.space = frame.space
 14        self.frame = frame     # turned into None when frame_finished_execution
 15        self.pycode = frame.pycode
 16        self.running = False
 17        if self.pycode.co_flags & CO_YIELD_INSIDE_TRY:
 18            self.register_finalizer(self.space)
 19
 20    def descr__repr__(self, space):
 21        if self.pycode is None:
 22            code_name = '<finished>'
 23        else:
 24            code_name = self.pycode.co_name
 25        addrstring = self.getaddrstring(space)
 26        return space.wrap("<generator object %s at 0x%s>" %
 27                          (code_name, addrstring))
 28
 29    def descr__reduce__(self, space):
 30        from pypy.interpreter.mixedmodule import MixedModule
 31        w_mod    = space.getbuiltinmodule('_pickle_support')
 32        mod      = space.interp_w(MixedModule, w_mod)
 33        new_inst = mod.get('generator_new')
 34        w        = space.wrap
 35        if self.frame:
 36            w_frame = self.frame._reduce_state(space)
 37        else:
 38            w_frame = space.w_None
 39
 40        tup = [
 41            w_frame,
 42            w(self.running),
 43            ]
 44
 45        return space.newtuple([new_inst, space.newtuple([]),
 46                               space.newtuple(tup)])
 47
 48    def descr__setstate__(self, space, w_args):
 49        from rpython.rlib.objectmodel import instantiate
 50        args_w = space.unpackiterable(w_args)
 51        w_framestate, w_running = args_w
 52        if space.is_w(w_framestate, space.w_None):
 53            self.frame = None
 54            self.space = space
 55            self.pycode = None
 56        else:
 57            frame = instantiate(space.FrameClass)   # XXX fish
 58            frame.descr__setstate__(space, w_framestate)
 59            GeneratorIterator.__init__(self, frame)
 60        self.running = self.space.is_true(w_running)
 61
 62    def descr__iter__(self):
 63        """x.__iter__() <==> iter(x)"""
 64        return self.space.wrap(self)
 65
 66    def descr_send(self, w_arg=None):
 67        """send(arg) -> send 'arg' into generator,
 68return next yielded value or raise StopIteration."""
 69        return self.send_ex(w_arg)
 70
 71    def send_ex(self, w_arg, operr=None):
 72        pycode = self.pycode
 73        if pycode is not None:
 74            if jit.we_are_jitted() and should_not_inline(pycode):
 75                generatorentry_driver.jit_merge_point(gen=self, w_arg=w_arg,
 76                                                    operr=operr, pycode=pycode)
 77        return self._send_ex(w_arg, operr)
 78
 79    def _send_ex(self, w_arg, operr):
 80        space = self.space
 81        if self.running:
 82            raise oefmt(space.w_ValueError, "generator already executing")
 83        frame = self.frame
 84        if frame is None:
 85            # xxx a bit ad-hoc, but we don't want to go inside
 86            # execute_frame() if the frame is actually finished
 87            if operr is None:
 88                operr = OperationError(space.w_StopIteration, space.w_None)
 89            raise operr
 90
 91        last_instr = jit.promote(frame.last_instr)
 92        if last_instr == -1:
 93            if w_arg and not space.is_w(w_arg, space.w_None):
 94                raise oefmt(space.w_TypeError,
 95                            "can't send non-None value to a just-started "
 96                            "generator")
 97        else:
 98            if not w_arg:
 99                w_arg = space.w_None
100        self.running = True
101        try:
102            try:
103                w_result = frame.execute_frame(w_arg, operr)
104            except OperationError:
105                # errors finish a frame
106                self.frame = None
107                raise
108            # if the frame is now marked as finished, it was RETURNed from
109            if frame.frame_finished_execution:
110                self.frame = None
111                raise OperationError(space.w_StopIteration, space.w_None)
112            else:
113                return w_result     # YIELDed
114        finally:
115            frame.f_backref = jit.vref_None
116            self.running = False
117
118    def descr_throw(self, w_type, w_val=None, w_tb=None):
119        """x.throw(typ[,val[,tb]]) -> raise exception in generator,
120return next yielded value or raise StopIteration."""
121        if w_val is None:
122            w_val = self.space.w_None
123        return self.throw(w_type, w_val, w_tb)
124
125    def throw(self, w_type, w_val, w_tb):
126        from pypy.interpreter.pytraceback import check_traceback
127        space = self.space
128
129        msg = "throw() third argument must be a traceback object"
130        if space.is_none(w_tb):
131            tb = None
132        else:
133            tb = check_traceback(space, w_tb, msg)
134
135        operr = OperationError(w_type, w_val, tb)
136        operr.normalize_exception(space)
137        return self.send_ex(space.w_None, operr)
138
139    def descr_next(self):
140        """x.next() -> the next value, or raise StopIteration"""
141        return self.send_ex(self.space.w_None)
142
143    def descr_close(self):
144        """x.close(arg) -> raise GeneratorExit inside generator."""
145        space = self.space
146        try:
147            w_retval = self.throw(space.w_GeneratorExit, space.w_None,
148                                  space.w_None)
149        except OperationError as e:
150            if e.match(space, space.w_StopIteration) or \
151                    e.match(space, space.w_GeneratorExit):
152                return space.w_None
153            raise
154
155        if w_retval is not None:
156            raise oefmt(space.w_RuntimeError,
157                        "generator ignored GeneratorExit")
158
159    def descr_gi_frame(self, space):
160        if self.frame is not None and not self.frame.frame_finished_execution:
161            return self.frame
162        else:
163            return space.w_None
164
165    def descr_gi_code(self, space):
166        return self.pycode
167
168    def descr__name__(self, space):
169        if self.pycode is None:
170            code_name = '<finished>'
171        else:
172            code_name = self.pycode.co_name
173        return space.wrap(code_name)
174
175    # Results can be either an RPython list of W_Root, or it can be an
176    # app-level W_ListObject, which also has an append() method, that's why we
177    # generate 2 versions of the function and 2 jit drivers.
178    def _create_unpack_into():
179        jitdriver = jit.JitDriver(greens=['pycode'],
180                                  reds=['self', 'frame', 'results'],
181                                  name='unpack_into')
182
183        def unpack_into(self, results):
184            """This is a hack for performance: runs the generator and collects
185            all produced items in a list."""
186            # XXX copied and simplified version of send_ex()
187            space = self.space
188            if self.running:
189                raise oefmt(space.w_ValueError, "generator already executing")
190            frame = self.frame
191            if frame is None:    # already finished
192                return
193            self.running = True
194            try:
195                pycode = self.pycode
196                while True:
197                    jitdriver.jit_merge_point(self=self, frame=frame,
198                                              results=results, pycode=pycode)
199                    try:
200                        w_result = frame.execute_frame(space.w_None)
201                    except OperationError as e:
202                        if not e.match(space, space.w_StopIteration):
203                            raise
204                        break
205                    # if the frame is now marked as finished, it was RETURNed from
206                    if frame.frame_finished_execution:
207                        break
208                    results.append(w_result)     # YIELDed
209            finally:
210                frame.f_backref = jit.vref_None
211                self.running = False
212                self.frame = None
213        return unpack_into
214    unpack_into = _create_unpack_into()
215    unpack_into_w = _create_unpack_into()
216
217    def _finalize_(self):
218        # This is only called if the CO_YIELD_INSIDE_TRY flag is set
219        # on the code object.  If the frame is still not finished and
220        # finally or except blocks are present at the current
221        # position, then raise a GeneratorExit.  Otherwise, there is
222        # no point.
223        if self.frame is not None:
224            block = self.frame.lastblock
225            while block is not None:
226                if not isinstance(block, LoopBlock):
227                    self.descr_close()
228                    break
229                block = block.previous
230
231
232def get_printable_location_genentry(bytecode):
233    return '%s <generator>' % (bytecode.get_repr(),)
234generatorentry_driver = jit.JitDriver(greens=['pycode'],
235                                      reds=['gen', 'w_arg', 'operr'],
236                                      get_printable_location =
237                                          get_printable_location_genentry,
238                                      name='generatorentry')
239
240from pypy.tool.stdlib_opcode import HAVE_ARGUMENT, opmap
241YIELD_VALUE = opmap['YIELD_VALUE']
242
243@jit.elidable_promote()
244def should_not_inline(pycode):
245    # Should not inline generators with more than one "yield",
246    # as an approximative fix (see issue #1782).  There are cases
247    # where it slows things down; for example calls to a simple
248    # generator that just produces a few simple values with a few
249    # consecutive "yield" statements.  It fixes the near-infinite
250    # slow-down in issue #1782, though...
251    count_yields = 0
252    code = pycode.co_code
253    n = len(code)
254    i = 0
255    while i < n:
256        c = code[i]
257        op = ord(c)
258        if op == YIELD_VALUE:
259            count_yields += 1
260        i += 1
261        if op >= HAVE_ARGUMENT:
262            i += 2
263    return count_yields >= 2