From: Leopold Toetsch <[EMAIL PROTECTED]> Date: Sun, 24 Sep 2006 13:16:08 +0200
Am Sonntag, 24. September 2006 03:43 schrieb Bob Rogers: > The attached patch creates a C<return_stack> in C<Parrot_Context> for > the exclusive use of the C<bsr/jsr/ret> ops. herwise. Is that right? Separating stacks is a good thing, as code paths are simplified. Q: do we really want or need a return stack per context? The more I think about it, the more it makes sense [but see my change of heart below]. The return stack needs to be scoped to the context anyway, so that the code doesn't accidentally C<ret> into the caller's sub. Putting the stack in the context means that stack underflow is detected naturally. Also, it currently works to C<.return> from a C<bsr> subroutine, and the return addresses are automatically popped. Whether or not any code depends on that, and whether or not it should be spec'ed to do that, we'd still have to add extra code to implement it either way. bsr/ret ought to be as slim as possible and it'll not mix with CPS anyway. Absolutely. [Except that, as shown below, it does mix, being part of the dynamic environment.] > However, since C<return_stack> entries always contain a > STACK_ENTRY_DESTINATION with STACK_CLEANUP_NULL, and are only ever > referenced from their owning context, using a C<Stack_Chunk> to store > them is way overkill. Would it be better to design something more > specific to return addresses so that it can be lighter in weight? Indeed. One ResizableIntegerArray in interpreter would do it. leo Heck, a dynamically-resized "void **" array and an index would do it. Then it wouldn't require memory allocation at each call. And that applies regardless of where we decide to put it. Oops; stop the presses; I just thought of a problem. Having a separate stack for return addresses means that the return stack state is not captured by continuations. Neither is the control stack at present, but this patch just splits that one bug into two. Please bear with me here: For a long time now, continuations have been imprecise about restoring the dynamic environment (as currently implemented via the control_stack slot). This is because the continuation points to the context which in turn points to the dynamic environment, so when you invoke a continuation, you return to the right context but with whatever dynamic environment the context was using when it was last exited. This is clearly a bug. It's not very noticeable when the only things on the dynamic environment are error handlers (which have a different mechanism and therefore work correctly anyway), actions, and C<bsr> return addresses, but the consequences will get more serious when we add dynamic bindings to the mix. The attached patch adds three Continuation test cases, the first of which illustrates this bug using C<bsr>. (I should probably also add cases that use C<pushaction> and C<push_eh>, for completeness.) This is an updated version from the patch I posted on 26-July and subsequently withdrew [1]. As I mentioned then, the right thing to do to fix all of this is to (a) move the dynamic environment binding slot into the interpreter, and (b) capture it along with the current context when taking a continuation. This is the next step on my "hit list" of dynamic environment jobs (it's been there for nearly a year now), and if C<Stack_Chunk> objects are indeed garbage-collected, it will be a lot easier than I had feared. But when that happens, if return addresses are sitting in a separate stack (whether in the context or the interpreter), they will still be subject to the same bug. Of course, the C<return_stack> state could also be captured in each continuation, but that seems like overkill. Unless you have strong opinions, I think I would prefer to keep return addresses on the control stack. I'll also assume I should commit the new continuation.t cases. -- Bob [1] See "Partial fix to make closures invoke actions". Last Tuesday's commit to consolidate stack unwinding had a similar effect; I hope some day to extend it to support dynamic binding and PDD23 error handling without any continuation barriers.
Index: t/pmc/continuation.t =================================================================== --- t/pmc/continuation.t (revision 14696) +++ t/pmc/continuation.t (working copy) @@ -6,7 +6,7 @@ use warnings; use lib qw( . lib ../lib ../../lib ); use Test::More; -use Parrot::Test tests => 1; +use Parrot::Test tests => 4; =head1 NAME @@ -33,4 +33,165 @@ ok 1 OUT +pir_output_is(<<'CODE', <<'OUT', 'continuations preserve bsr/ret state.', todo => "BUG: continuations don't preserve the control_stack."); +## 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 + +pir_output_like(<<'CODE', <<'OUT', 'continuation action context'); +## this makes sure that returning via the continuation causes the action to be +## invoked in the right dynamic context (i.e. without the error handler). +.sub test_cont_action :main + .local pmc cont + cont = new .Continuation + set_addr cont, continued + _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" + $P0 = new .Exception + $P0["_message"] = "something happened" + throw $P0 +.end + +## invoke the continuation within an error handler. +.sub _test_2 + .param pmc n + .param pmc cont + push_eh L3 + cont() + print "oops" +L3: + .local pmc exception + .get_results (exception, $S0) + print "Error: " + print exception + print "\n" +.end +CODE +/\A_test_1 +unwinding +something happened +current instr/ +OUT + +# end of tests.