SUSPEND (continuations)

Steven D. Majewski (sdm7g@minsky.med.virginia.edu)
Mon, 9 May 1994 21:46:41 -0400

I have a "proof-of-concept" version of resumable-function mods to Python
ceval.c. There is one major identified bug it it (more on that later.)
It has been hacked out with the method that was easiest to implement -
it needs better error checking, which may also require moving some code
around, but I wanted to keep the differences between it and "vanilla"
ceval.c to a minimum, and well bounded with #ifdef's.

I did not completely agree with Tim and Guido's analysis of the
problems involved. This is not the refutation of their view - only
the tool I need to discover if they are correct or not. i.e. all this
implements is "resumable functions" - I have yet to demonstrate that
a more complete threading and/or coroutine control can be built in
a similar manner. ( SO it's only proof of half the concept, so far! ;-)

I tried writing a class to package and hide the state of the
"continuation" of the suspended function. That's where I ran into
the bug. I forgot that since I am NOT calling newframeobject, the
object returned by the SUSPEND is the *same* frameobject ( with
perhaps different values. ) each time. That is consistant with
the sort of semantics I was shooting for, but I didn't fully take
tyhis into account in figuring out when to INCREF/DECREF the
object. As a result, trying to reassign the continuation to
the same variable the held the previous on appears to cause it
to disappear - it's getting DECREF-ed out of existance when
the first instance is destroyed.

module systst.py contains:

def g( n ):
print 'CALL', g
for i in range(n):
print (g,i)
return i # SUSPEND (f,i)
print 'RESUME:',(g,i)
print 'RETURN:', g
return None

module SUSTST.pyc is sustst.pyc with the byte-code for RETURN
replaced with a new code for SUSPEND, which returns a tuple
( value, frameobject ), where frameobject has been extended
with an extra field to hold Virtual-Machine stack pointer.

ceval.c eval_code() has been modified to add a case for SUSPEND
byte code, and to check if passed "codeobject" is actually a
frameobject, in which case newframeobject() is not done,and
fields are loaded from the saved frame instead of using default
initial values. call_object has also had a case added to make
frameobjects callable.

./python
Python 1.0.2 (May 9 1994)
Copyright 1991-1994 Stichting Mathematisch Centrum, Amsterdam
>>> from SUSTST import g
>>> v,f = g(6)
CALL <function g at 2006bc08>
(<function g at 2006bc08>, 0)
SUSPEND... # the tab indented text is printfed from eval_code()

>>> F = [f]
>>> v,f = f()
call_object( frameobject, ... )
WHY_CONT: eval_code( frameobject, ...
continuing ...
SUSPEND: reset instr and stack pointer.
and continuing...
RESUME: (<function g at 2006bc08>, 0)
(<function g at 2006bc08>, 1)
SUSPEND...

>>> v,f
(1, <frame object at 2006b9c8>)
>>> F.append(f)
>>> v,f = f()
call_object( frameobject, ... )
WHY_CONT: eval_code( frameobject, ...
continuing ...
SUSPEND: reset instr and stack pointer.
and continuing...
RESUME: (<function g at 2006bc08>, 1)
(<function g at 2006bc08>, 2)
SUSPEND...

>>> v
2
>>> F.append(f) # Keeping frame alive in list F
>>> F # note that they are the same object!
[<frame object at 2006b9c8>, <frame object at 2006b9c8>, <frame object at 2006b9c8>]
>>> v,h = g(4) # start a new instance of function
CALL <function g at 2006bc08>
(<function g at 2006bc08>, 0)
SUSPEND...

>>> v
0
>>> H = [h]
>>> fff,hhh=f(),h() # old, new
call_object( frameobject, ... )
WHY_CONT: eval_code( frameobject, ...
continuing ...
SUSPEND: reset instr and stack pointer.
and continuing...
RESUME: (<function g at 2006bc08>, 2)
(<function g at 2006bc08>, 3)
SUSPEND...

call_object( frameobject, ... )
WHY_CONT: eval_code( frameobject, ...
continuing ...
SUSPEND: reset instr and stack pointer.
and continuing...
RESUME: (<function g at 2006bc08>, 0)
(<function g at 2006bc08>, 1)
SUSPEND...

>>> fff # value from older instance of suspended function.
(3, <frame object at 2006b9c8>)
>>> hhh # value from newer instance
(1, <frame object at 2006c198>)
>>>

SUSPEND is:

#ifdef SDM7G_CONT
case SUSPEND:
fprintf( stderr, "\n\tSUSPEND...\n" );
retval = POP();
f->f_lasti = INSTR_OFFSET(); # save VM instruction
f->f_stackptr = stack_pointer; # and stack pointer
current_frame = f->f_back; # prev. frame
x = newtupleobject( 2 ); # return a tuple of
err = settupleitem( x, 0, retval ); # (value,
err = settupleitem( x, 1, f ); # frame)
/* needs some error checking */
retval = x;
return retval;
#endif

If eval_code is called with a frameobject:

if ( is_frameobject( co ) ) {
fprintf( stderr, "\n\t WHY_CONT: eval_code( frameobject, ... \n" );
why = WHY_CONT;
f = (frameobject *) co;
co = f->f_code;
globals = f->f_globals;
locals = f->f_locals;
[ ... etc... ]

then variables are loaded from the saved values in the frameobject
including the Virtual Machine's next_instruction pointer, stack pointer
and fastlocals list. ( a field to keep the stackpointer was the
additional field added to frameobject. )

JUMPBY(f->f_lasti) ; /* next_instr += */
stack_pointer = f->f_stackptr;
fastlocals = (listobject *) f->f_fastlocals ;

The complete diffs are slightly more complex - there are a couple
of things that shouldn't get done in the RESUME case, and since
the thinks that DO get done are in different places, a new why
code, WHY_CONT, was added to indicate that that is the case after
code and frames have been assigned.

-- Steve Majewski (804-982-0831) <sdm7g@Virginia.EDU> --
-- UVA Department of Molecular Physiology and Biological Physics --
-- Box 449 Health Science Center Charlottesville,VA 22908 --
[ "Cognitive Science is where Philosophy goes when it dies ...
if it hasn't been good!" - Jerry Fodor ]