[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