From: Allison Randal <[EMAIL PROTECTED]> Date: Wed, 05 Jul 2006 00:02:53 -0700
Bob Rogers wrote: > If, as seems likely, exception bookkeeping is moved to a separate > stack in the interpreter (with or without dynamic-wind actions), then > C<bsr>/C<ret> addresses can stay in the Parrot_Context, and all of > pdd23_exceptions.pod that is quoted below ceases to be problematic. > Does that seem reasonable? Yeah, that's the current best solution (the last time Chip and I talked on Saturday). I'd like avoid a stack entirely, but this is a straightforward first attack on the problem. If we implement a separate exception stack now and run into problems, we can refine and refactor later. Were you thinking of using the same stack for dynamic binding context and stack-winding actions as well? Or would these go on separate stacks? In what follows, I will speak of these collectively as the "dynamic state stack", with the understanding that it may need to be pluralized. (And the further understanding that continuations and coroutines can make it branch, just like the Parrot_Context => RetContinuation "stack". So maybe we need a better word?) It's an encouraging sign that we've got hold of a good idea when the same solution occurs to others independently. :) Thanks, Allison Well, yesterday I thought of a problem with this. So maybe that just means that we're all sharing the same delusion. ;-} The snag is that calling a continuation would return to the Parrot_Context with the return stack state as it was when the context was last left, which could be different than when the continuation was taken. What I would expect to happen is that the continuation also captures the return stack state, as part of the dynamic context, so I would argue that this is a bug. This is currently not a problem with respect to error handling, since the stack-unwinding done by C<throw> implicitly pops return addresses as well, because it's the same stack. However, it does fail now with other uses of continuations, as shown by the first new test case in the attached patch. One could argue that maybe it is not worth fixing the general case. However, separating the stacks as proposed would extend this bug to cover the C<throw> case as well, and it is probably not acceptable to say that you can't depend on the return stack after error recovery. True? Continuations also don't run actions (as shown by the second new test case), and they don't preserve error handlers for the same reason -- these bugs are much more serious. Furthermore, I assume dynamic binding would be similarly affected. (In fact, I've been itching to fix all of this for almost six months now, but I think it needs to be done as part of a general cleanup of dynamic state bookkeeping -- I now have more tuits, but I don't yet understand the changes you are proposing well enough to get started.) The obvious solution is to save the current return stack TOS along with that of the dynamic state stack(s) in the continuation, so that it can be restored when the continuation is invoked. The drawback is that doing so complicates the storage management of return stack entries. This is the same complication that must be borne by dynamic state stack entries, but it seems a shame to have to extend it to something that otherwise would obviously belong strictly to the Parrot_Context in which it was pushed. So maybe there needs to be One Dynamic Stack (or whatever we call it), so that we can unify the bookkeeping? This would also require that all of these things nest properly, but that's no change from the current implementation -- and nobody seems to have complained. -- Bob
Index: t/pmc/continuation.t =================================================================== --- t/pmc/continuation.t (revision 13078) +++ t/pmc/continuation.t (working copy) @@ -33,6 +33,114 @@ ok 1 OUT +$TODO = "BUG: continuations don't preserve the control_stack."; +pir_output_is(<<'CODE', <<'OUT', 'continuations preserve bsr/ret state.'); +## Here is a trace of execution, keyed by labels. +## L1: bsr to rtn1 +## rtn1: create a continuation that directs us to L6, and (we expect) captures +## captures the whole dynamic state, including the return address to L3. +## L3: return back to main +## L4: if we're here the first time, call rtn2 +## rtn2: call the continuation from that routine. +## L6: print "Continuation called." and return, which should take us . . . +## L3: here the second time, where we print "done." and exit. +.sub test_control_cont :main +L1: + .local int return_count + .local pmc cont + return_count = 0 + bsr rtn1 +L3: + unless return_count goto L4 + print "done.\n" + end +L4: + inc return_count + bsr rtn2 + print "Oops; shouldn't have returned from rtn2.\n" + end +L6: + print "Continuation called.\n" + ret +rtn1: + print "Taking continuation.\n" + cont = new .Continuation + set_addr cont, L6 + ret +rtn2: + print "Calling continuation.\n" + cont() + ret +.end +CODE +Taking continuation. +Calling continuation. +Continuation called. +done. +OUT + +pir_output_is(<<'CODE', <<'OUT', 'continuations call actions.'); +## the test_cont_action sub creates a continuation and passes it to _test_1 +## twice: the first time returns normally, the second time returns via the +## continuation. +.sub test_cont_action :main + ## debug 0x80 + .local pmc cont + cont = new .Continuation + set_addr cont, continued + _test_1(4, cont) + _test_1("bar", cont) + print "oops; no " +continued: + print "continuation called.\n" +.end + +## set up C<pushaction> cleanup, and pass our arguments to _test_2. +.sub _test_1 + .param pmc arg1 + .param pmc cont + print "_test_1\n" + .const .Sub $P43 = "___internal_test_1_0_" + pushaction $P43 + $P50 = _test_2(arg1, cont) + print "got " + print $P50 + print "\n" + .return ($P50) +.end + +## cleanup sub used by _test_1, which just shows whether or not the action was +## called at the right time. +.sub ___internal_test_1_0_ + .local pmc arg1 + print "unwinding\n" + .return () +.end + +## return 3*n if n is an integer, else invoke the continuation. +.sub _test_2 + .param pmc n + .param pmc cont + typeof $I40, n + if $I40 != .Integer goto L3 + $P44 = n_mul n, 3 + .return ($P44) +L3: + cont() +.end +CODE +_test_1 +got 12 +unwinding +_test_1 +unwinding +continuation called. +OUT + + +$TODO = ''; + + # remember to change the number of tests :-) -BEGIN { plan tests => 1; } +BEGIN { plan tests => 3; }