[pypy-commit] pypy stacklet: Start the implementation.

arigo noreply at buildbot.pypy.org
Wed Aug 17 17:36:25 CEST 2011


Author: Armin Rigo <arigo at tunes.org>
Branch: stacklet
Changeset: r46562:4d6c7ad30792
Date: 2011-08-17 17:40 +0200
http://bitbucket.org/pypy/pypy/changeset/4d6c7ad30792/

Log:	Start the implementation.

diff --git a/pypy/module/_continuation/__init__.py b/pypy/module/_continuation/__init__.py
--- a/pypy/module/_continuation/__init__.py
+++ b/pypy/module/_continuation/__init__.py
@@ -4,16 +4,26 @@
 class Module(MixedModule):
     """This module exposes 'one-shot continuation containers'.
 
-A 'flexibleframe' object from this module is a container that stores a
-one-shot continuation.  It is a frame-like object, attached as the
-'f_back' of the entry point function that it calls, and with an 'f_back'
-of its own.  Unlike normal frames, the continuation exposed in this
-'f_back' can be changed, with the switch() and switch2() methods.
+A 'continuation' object from this module is a container that stores a
+one-shot continuation.  It is similar in purpose to the 'f_back'
+attribute of frames, which points to where execution should continue
+after this frame finishes.  The difference is that it will be changed
+(often repeatedly) before the frame actually returns.
+
+To make a 'continuation' object, call 'continuation' with a callable and
+optional extra arguments.  When later you switch() to the continuation,
+the callable is invoked wih the continuation as extra first argument.
+
+At this point, the one-shot continuation stored in the continuation
+container points to the caller of switch().  When switch() is called
+again, this one-shot continuation is exchanged with the current one; it
+means that the caller of switch() is suspended, its continuation stored
+in the container, and the old continuation from the container is
+resumed.
 
 Flexible frames are internally implemented using stacklets.  Stacklets
-are a bit more primitive (they are one-shot continuations, usable only
-once) but that concept only really works in C, not in Python, notably
-because of exceptions.
+are a bit more primitive (they are really one-shot continuations), but
+that idea only works in C, not in Python, notably because of exceptions.
 """
 
     appleveldefs = {
@@ -22,5 +32,5 @@
     }
 
     interpleveldefs = {
-        'flexibleframe': 'interp_continuation.W_FlexibleFrame',
+        'continuation': 'interp_continuation.W_Continuation',
     }
diff --git a/pypy/module/_continuation/app_continuation.py b/pypy/module/_continuation/app_continuation.py
--- a/pypy/module/_continuation/app_continuation.py
+++ b/pypy/module/_continuation/app_continuation.py
@@ -3,7 +3,7 @@
     "Usage error of the _continuation module."
 
 
-from _continuation import flexibleframe
+import _continuation
 
 
 class generator(object):
@@ -18,7 +18,7 @@
         return genlet(self.__func__, *args, **kwds)
 
 
-class genlet(flexibleframe):
+class genlet(_continuation.continuation):
 
     def __iter__(self):
         return self
diff --git a/pypy/module/_continuation/interp_continuation.py b/pypy/module/_continuation/interp_continuation.py
--- a/pypy/module/_continuation/interp_continuation.py
+++ b/pypy/module/_continuation/interp_continuation.py
@@ -1,217 +1,135 @@
 import sys
 from pypy.rpython.lltypesystem import lltype
 from pypy.rlib import jit
-from pypy.rlib.rstacklet import StackletThread
+from pypy.rlib.rstacklet import StackletThread, get_null_handle
 from pypy.rlib.objectmodel import we_are_translated
 from pypy.interpreter.error import OperationError
 from pypy.interpreter.executioncontext import ExecutionContext
 from pypy.interpreter.baseobjspace import Wrappable
 from pypy.interpreter.typedef import TypeDef
 from pypy.interpreter.gateway import interp2app
+from pypy.interpreter.pycode import PyCode
 from pypy.rlib.debug import ll_assert, fatalerror
 
-#
-# Note: a "continuation" at app-level is called a "stacklet" here.
-#
+
+class W_Continuation(Wrappable):
+
+    def __init__(self, space):
+        self.space = space
+        self.sthread = None
+        self.h = get_null_handle(space.config)
+
+    def build_sthread(self):
+        space = self.space
+        ec = space.getexecutioncontext()
+        sthread = ec.stacklet_thread
+        if not sthread:
+            sthread = ec.stacklet_thread = SThread(space, ec)
+        self.sthread = sthread
+        return sthread
+
+    def check_sthread(self):
+        ec = self.space.getexecutioncontext()
+        if ec.stacklet_thread is not self.sthread:
+            raise geterror(self.space, "inter-thread support is missing")
+
+    def descr_init(self, w_callable):
+        if self.h:
+            raise geterror(self.space, "continuation already filled")
+        sthread = self.build_sthread()
+        start_state.origin = self
+        start_state.w_callable = w_callable
+        self.h = self.sthread.new(new_stacklet_callback)
+        if not self.h:
+            raise getmemoryerror(self.space)
+
+    def descr_switch(self, w_value=None):
+        start_state.w_value = w_value
+        self.h = self.sthread.switch(self.h)
+        w_value = start_state.w_value
+        start_state.w_value = None
+        return w_value
+
+
+def W_Continuation___new__(space, w_subtype, __args__):
+    r = space.allocate_instance(W_Continuation, w_subtype)
+    r.__init__(space)
+    return space.wrap(r)
+
+
+W_Continuation.typedef = TypeDef(
+    'continuation',
+    __module__ = '_continuation',
+    __new__     = interp2app(W_Continuation___new__),
+    __init__    = interp2app(W_Continuation.descr_init),
+    switch      = interp2app(W_Continuation.descr_switch),
+    #is_pending = interp2app(W_Stacklet.is_pending),
+    )
+
+
+# ____________________________________________________________
+
+# Continuation objects maintain a dummy frame object in order to ensure
+# that the 'f_back' chain is consistent.  We hide this dummy frame
+# object by having a dummy code object with hidden_applevel=True.
+
+class ContinuationState:
+    def __init__(self, space):
+        self.space = space 
+        w_module = space.getbuiltinmodule('_continuation')
+        self.w_error = space.getattr(w_module, space.wrap('error'))
+        self.dummy_pycode = PyCode(space, 0, 0, 0, 0,
+                                   '', [], [], [], '',
+                                   '', 0, '', [], [],
+                                   hidden_applevel=True)
+        self.w_dummy_globals = space.newdict()
+
+def make_fresh_frame(space):
+    cs = space.fromcache(ContinuationState)
+    return space.FrameClass(space, cs.dummy_pycode,
+                            cs.w_dummy_globals, closure=None)
+
+def geterror(space, message):
+    cs = space.fromcache(ContinuationState)
+    return OperationError(cs.w_error, space.wrap(message))
+
+def getmemoryerror(space):
+    return OperationError(space.w_MemoryError, space.w_None)
+getmemoryerror._annlowlevel_ = 'specialize:memo'
+
+# ____________________________________________________________
+
 
 class SThread(StackletThread):
 
     def __init__(self, space, ec):
         StackletThread.__init__(self, space.config)
-        w_module = space.getbuiltinmodule('_continuation')
         self.space = space
         self.ec = ec
-        self.w_error = space.getattr(w_module, space.wrap('error'))
-        self.pending_exception = None
-        self.current_stack = StackTreeNode(None)
-
-    def new_stacklet_object(self, h, in_new_stack):
-        # Called when we switched somewhere else.  'h' is the handle of
-        # the new stacklet, i.e. what we just switched away from.
-        # (Important: only called when the switch was successful, not
-        # when it raises MemoryError.)
-        #
-        # Update self.current_stack.
-        in_old_stack = self.current_stack
-        ll_assert(in_old_stack.current_stacklet is None,
-                  "in_old_stack should not have a current_stacklet")
-        ll_assert(in_new_stack is not in_old_stack,
-                  "stack switch: switch to itself")
-        in_new_stack.current_stacklet = None
-        self.current_stack = in_new_stack
-        #
-        if self.pending_exception is None:
-            #
-            # Normal case.
-            if self.is_empty_handle(h):
-                return self.space.w_None
-            else:
-                res = W_Stacklet(self, h)
-                in_old_stack.current_stacklet = res
-                return self.space.wrap(res)
-        else:
-            # Got an exception; re-raise it.
-            e = self.pending_exception
-            self.pending_exception = None
-            if not self.is_empty_handle(h):
-                self.destroy(h)
-            if we_are_translated():
-                raise e
-            else:
-                tb = self.pending_tb
-                del self.pending_tb
-                raise e.__class__, e, tb
 
 ExecutionContext.stacklet_thread = None
 
+# ____________________________________________________________
 
-class StackTreeNode(object):
-    # When one stacklet gets an exception, it is propagated to the
-    # "parent" stacklet.  Parents make a tree.  Each stacklet is
-    # conceptually part of a stack that doesn't change when we switch
-    # away and back.  The "parent stack" is the stack from which we
-    # created the new() stack.  (This part works like greenlets.)
-    #
-    # It is important that we have *no* app-level access to this tree.
-    # It would break the 'composability' of stacklets.
-    #
-    def __init__(self, parent):
-        self.parent = parent
-        self.current_stacklet = None
 
-    def raising_exception(self):
-        while self.current_stacklet is None:
-            self = self.parent
-            if self is None:
-                fatalerror("StackTreeNode chain is empty!")
-        res = self.current_stacklet
-        self.current_stacklet = None
-        try:
-            return res.consume_handle()
-        except OperationError:
-            fatalerror("StackTreeNode contains an empty stacklet")
-            raise ValueError    # annotator hack, but cannot return
+class StartState:   # xxx a single global to pass around the function to start
+    def clear(self):
+        self.origin = None
+        self.w_callable = None
+        self.args = None
+        self.w_value = None
+start_state = StartState()
+start_state.clear()
 
-    def __repr__(self):
-        s = '|>'
-        k = self
-        while k:
-            s = hex(id(k)) + ' ' + s
-            k = k.parent
-        s = '<StackTreeNode |' + s
-        return s
-
-
-class W_Stacklet(Wrappable):
-    def __init__(self, sthread, h):
-        self.sthread = sthread
-        self.h = h
-
-    def __del__(self):
-        h = self.h
-        if h:
-            self.h = self.sthread.get_null_handle()
-            self.sthread.destroy(h)
-
-    def consume_handle(self):
-        h = self.h
-        if h:
-            self.h = self.sthread.get_null_handle()
-            return h
-        else:
-            space = self.sthread.space
-            raise OperationError(
-                self.sthread.w_error,
-                space.wrap("continuation has already been resumed"))
-
-    def switch(self, space):
-        sthread = self.sthread
-        ec = sthread.ec
-        stack = sthread.current_stack
-        saved_frame_top = ec.topframeref
-        try:
-            h1 = self.consume_handle()
-            try:
-                h = sthread.switch(h1)
-            except MemoryError:
-                self.h = h1    # try to restore
-                raise
-        finally:
-            ec.topframeref = saved_frame_top
-        return sthread.new_stacklet_object(h, stack)
-
-    def is_pending(self, space):
-        return space.newbool(bool(self.h))
-
-W_Stacklet.typedef = TypeDef(
-    'Continuation',
-    __module__ = '_continuation',
-    switch     = interp2app(W_Stacklet.switch),
-    is_pending = interp2app(W_Stacklet.is_pending),
-    )
-W_Stacklet.acceptable_as_base_class = False
-
-
-class StartState:
-    sthread = None  # xxx a single global to pass around the function to start
-    w_callable = None
-    args = None
-start_state = StartState()
 
 def new_stacklet_callback(h, arg):
-    sthread = start_state.sthread
+    self = start_state.origin
     w_callable = start_state.w_callable
-    args = start_state.args
-    start_state.sthread = None
-    start_state.w_callable = None
-    start_state.args = None
-    ready = False
-    parentstacknode = sthread.current_stack
+    start_state.clear()
+    self.h = self.sthread.switch(h)
     #
-    try:
-        space = sthread.space
-        stacknode = StackTreeNode(parentstacknode)
-        stacklet = sthread.new_stacklet_object(h, stacknode)
-        ready = True
-        args = args.prepend(stacklet)
-        w_result = space.call_args(w_callable, args)
-        #
-        try:
-            result = space.interp_w(W_Stacklet, w_result)
-            return result.consume_handle()
-        except OperationError, e:
-            w_value = e.get_w_value(space)
-            msg = 'returning from _continuation.new: ' + space.str_w(w_value)
-            raise OperationError(e.w_type, space.wrap(msg))
+    space = self.space
+    w_result = space.call_function(w_callable, space.wrap(self))
     #
-    except Exception, e:
-        sthread.pending_exception = e
-        if not we_are_translated():
-            print >> sys.stderr
-            print >> sys.stderr, '*** exception in stacklet ***'
-            sthread.pending_tb = sys.exc_info()[2]
-            import traceback
-            traceback.print_exc(sthread.pending_tb)
-            print >> sys.stderr, '***'
-            #import pdb; pdb.post_mortem(sthread.pending_tb)
-        if ready:
-            return parentstacknode.raising_exception()
-        else:
-            return h      # corner case, try with just returning h...
-
-def stacklet_new(space, w_callable, __args__):
-    ec = space.getexecutioncontext()
-    sthread = ec.stacklet_thread
-    if not sthread:
-        sthread = ec.stacklet_thread = SThread(space, ec)
-    start_state.sthread = sthread
-    start_state.w_callable = w_callable
-    start_state.args = __args__
-    stack = sthread.current_stack
-    saved_frame_top = ec.topframeref
-    try:
-        ec.topframeref = jit.vref_None
-        h = sthread.new(new_stacklet_callback)
-    finally:
-        ec.topframeref = saved_frame_top
-    return sthread.new_stacklet_object(h, stack)
+    start_state.w_value = w_result
+    return self.h
diff --git a/pypy/module/_continuation/test/test_stacklet.py b/pypy/module/_continuation/test/test_stacklet.py
--- a/pypy/module/_continuation/test/test_stacklet.py
+++ b/pypy/module/_continuation/test/test_stacklet.py
@@ -10,20 +10,49 @@
                          'test_translated.py'))
 
     def test_new_empty(self):
-        from _continuation import new, callcc
+        from _continuation import continuation
         #
-        def empty_callback(h):
-            assert h.is_pending()
+        def empty_callback(c):
+            pass
+        #
+        c = continuation(empty_callback)
+        assert type(c) is continuation
+
+    def test_call_empty(self):
+        from _continuation import continuation
+        #
+        def empty_callback(c1):
+            assert c1 is c
             seen.append(1)
-            return h
+            return 42
         #
         seen = []
-        h = new(empty_callback)
-        assert h is None
+        c = continuation(empty_callback)
+        res = c.switch()
+        assert res == 42
         assert seen == [1]
-        h = callcc(empty_callback)
-        assert h is None
-        assert seen == [1, 1]
+
+    def test_no_init_after_started(self):
+        from _continuation import continuation, error
+        #
+        def empty_callback(c1):
+            raises(error, c1.__init__, empty_callback)
+            return 42
+        #
+        c = continuation(empty_callback)
+        res = c.switch()
+        assert res == 42
+
+    def test_no_init_after_finished(self):
+        from _continuation import continuation, error
+        #
+        def empty_callback(c1):
+            return 42
+        #
+        c = continuation(empty_callback)
+        res = c.switch()
+        assert res == 42
+        raises(error, c.__init__, empty_callback)
 
     def test_bogus_return_value(self):
         from _continuation import new


More information about the pypy-commit mailing list