Re: coroutines and continuations ( in Python? - long discussion )

Tim Peters (tim@ksr.com)
Thu, 05 May 94 23:29:27 -0400

> > [tim]
> > ... the _C_ runtime stack is also part of the current continuation ...

> [steve]
> ... the _C_ runtime stack is NOT a part of the context or current
> continuation - only the Python block and argument stacks and the
> current byte-code instruction pointer. We only have to keep state
> for the virtual machine - that's what makes it portable.

I believe you're going to find this isn't true, at both shallow and deep
levels. Guido mentioned the "deep" level: a call to a Python routine may
end up calling an external C routine that in turn calls another Python
routine. The continuation at that point is a mess.

The "shallow" level comes up in _any_ call today. E.g., here's the whole
program:

def f():
# want to save a continuation here
return 1

f()

The byte-code for the call to f calls the C routine call_object, which
in turn calls the C routine call_function, which in turn (an indirect
recursion) calls eval_code again. So by the time we get to the comment,
we have 3 C frames stacked up as part of the continuation. The
RETURN_VALUE code doesn't currently stay within the evaluation loop: it
exits eval_code, unwinding the C stack frames until it gets back to the
call_object call in the top-level invocation of eval_code.

"resfuncs" as originally presented were defined in such a way that all
that hair was irrelevant: they always returned directly to their caller,
and so unwound all intermediate C frames, and required capturing the
state in only one Python frameobject for resumption (that's what I was
getting at with the unclear "what to do with the back pointer is clear"
bit: all the relevant state was contained in one Python frameobject, so
the _chain_ of C frames that come with a _chain_ of frameobjects wasn't a
problem).

Guido mentioned that for a non-thread implementation of the Generator
class to work, and allowing .put ("suspend") in routines called _from_
the Generator function, he'd have to do the call_object/call_function bit
(& presumably the RETURN_VALUE part too) inline, to avoid the C frame
problems (i.e., to have the common call cases handled iteratively, within
the evaluation loop, rather than via recursive invocations of the
evaluation loop: it's the latter that sucks the C stack into the
continuation).

That would take care of the "shallow" problems, and may be a Good Thing
regardless (if, as seems likely, it cut the time to do a Python call).
But the "deep" problems remain.

> ... threads could be supported more portably by building coroutines
> into the interpreter virtual machine. ... If I give up on coroutines
> and/or continuations in the *language*, my fall back is to try to
> support portable threads via this mechanism.

I don't know -- it's certainly more complicated than I thought at first,
though! Even the obvious approach of building the notion of threads
directly into the interpreter, and having it explicitly time-slice them
at the byte-code level, gets into all sorts of trouble early on, and
again in large part because the C stack gets intertwined. I actually
don't see a clean solution short of making the "evaluation loop" an
honest-to-Guido plain _loop_.

BTW, I don't know whether the following got sent to the list, but a
certain well-known Python Fan had this to say about the language he's
forced to work on to pay for all his golf carts <wink>:

> Indeed. That's one of the reasons Perl 5 was written not to keep any
> of that stuff in the C stack. That's part of what I mean when I say
> that Perl 5 has a very flat interpreter.
>
> I do keep the context info on a stack, however. But it's all pushed and
> popped with macros, so it would be relatively easy to reimplement.
>
> On the other hand, I don't particularly like continuations... :-)

looking-for-love-in-all-the-wrong-places<grin>-ly y'rs - tim

Tim Peters tim@ksr.com
not speaking for Kendall Square Research Corp