From: Leopold Toetsch <[EMAIL PROTECTED]>
   Date: Mon, 18 Sep 2006 11:53:36 +0200

   Am Montag, 18. September 2006 03:56 schrieb Bob Rogers:
   >    The attached patch consolidates most of the existing stack-unwinding
   > code into Continuation:invoke; previously, RetContinuation:invoke and
   > find_exception_handler also did stack-unwinding, and none of the three
   > did it quite the same way.

   Very good.

Thanks.

   >    Here are the effects:
   >
   >    1.  Improved code sharing, a prerequisite for improving semantics.
   >
   >    2.  Actions are now triggered when a continuation is called, fixing
   > two TODO cases (and also two others which were looking for the buggy
   > behavior).  Previously, this only worked for exception handling and
   > RetContinuation.

   There are (ought to be) presumably 2 different kinds of continuations:
   1) I leave this call frame for now, but I'll be back
   2) I'm done with this call frame forever
   An exit_handler action would be invoked only for the 2nd case. 

Currently, a boolean is passed that is true for an Error_Handler exit
and false otherwise.  I think it would be more elegant if the
contination object itself was passed, and then implementers could do
'isa' testing to decide for themselves how to handle each case.

   Beyond that, the fact that a given continuation is done with a call
frame forever doesn't mean that there isn't somebody else who might be
able to return to it.  In such a case, Parrot can't know which is
supposed to be the last such exit.  So it seems more versatile to always
invoke it and let it decide for itself.

   And while at it, we probably want some more specialized (limited) 
   continuations with e.g. a given range of call frames, which the continuation 
   will not leave.

I'm not sure I see the advantage -- but that may depend on the
definition of "a given range".

   >    3.  Actions are no longer triggered as a side-effect of discovering
   > that there are no active exception handlers, which is more in the spirit
   > of PDD23.

   IMHO there is always an exception handler, assuming a toplevel default 
   handler, which is implicitely handling the "No exception handler found" case.
   Avoiding this side-effect seems to be the reason for the very complex and 
   costly search for active handlers, which I really don't like.

It need not be complex or costly; see below.

   And this is not the only side effect it avoids.  Currently, the only
way a handler can decline to handle a given error is to rethrow it,
destroying the more interesting parts of the stack.  When the last
handler rethrows, the default handler can only show the backtrace from
there, which is not likely to be very helpful.  If the default handler
were implemented explicitly, as if there were a C<push_eh> before
calling the main sub, there wouldn't be *anything* to backtrace.

   IMHO, the "default handler" for an interactive session should be the
debugger; for maximum utility, the debugger should have access to the
pristine state at the point of the error.  This is easy as long as
handlers avoid side-effects for errors they do not plan to handle.

   >    Questions:
   >
   >    1.  I notice that parameter passing is handled by Continuation:invoke
   > but not by RetContinuation:invoke.  Currently I have to use some
   > cumbersome "Am I a RetContinuation?" logic in what is supposed to be the
   > generic method in order to deal with this.  It would be nicer if they
   > both passed parameters in the same way.  Is there some reason this can't
   > be done?

   Currently set_returns and set_args are 2 different opcodes:

     .return (foo, bar)   # RetContinuation.invoke - set_returns
     cc(foo, bar)         # Continuation.invoke    - set_args

   But with a few changes, we could unify "results" value handling:
   - the current_results pointer *should* be part of the continuation structure 
   and not of the context like now (it is already in the Continuation)
   - then unifying set_returns and set_args would give us common 
   {Ret,}Continuation argument->results passing.

That sounds good.  It would be even better if this made it easy for C
code to intercept returns via a continuation.  But perhaps that is
covered by your Cfunc.pod proposal?

   >    2.  The exception handling case is not as efficient as it could be
   > (quadratic handler search followed by re-searching for the handler's
   > stack entry).  This would be helped by splitting the unwinding code into
   > a separate routine, and calling it from each of the invoke methods,
   > rather than trying to use inheritance.  But I assume this will need to
   > be rewritten anyway when PDD23 is fully implemented.  With that in mind,
   > is this OK for now?

   Given that it'll be rewritten yes ;-)

Good; thanks.

   >    3.  Around src/exceptions.c:268 (unpatched) there is an assignment of
   > e->entry_type to NO_STACK_ENTRY_TYPE, after a comment explaining why.
   > The patch disables this assignment, with no ill effect.  If the code is
   > still needed for some reason, it will have to be moved to
   > RetContinuation:invoke, but I don't have a test case to ensure that I've
   > done this right.  Should I just chuck it?

   It was IIRC needed for DOD. I.e. at some time there was a stale stack entry, 
   which caused troubles during stack marking. This could have been due to an 
   error elsewhere of course.

Hmm.  Then maybe I should do more testing.  I'll try throwing random
"sweep 1" calls in test cases that do C<pushaction>.

   >    Suggestions are cordially invited.  If I hear no complaints by
   > tomorrow night, I will assume all answers are "yes," and commit this (or
   > something close to it).

   Ok. BTW:

   +            && ! ret_continuation_p) {
               fprintf(stderr, "[oops; continuation %p of type %d "
                        "is trying to jump from runloop %d to runloop %d]\n",

   This meessage is triggered for exceptions too, which is wrong. Only 
   continuations that *would* possibly resume this runloop are a problem (i.e. 
   the 1) case above).

The TODO case I added to t/pmc/exception.t gives the following result:

        not ok 34 - pushaction: error while handling error # TODO runloop 
shenanigans
        #     Failed (TODO) test (t/pmc/exception.t at line 754)
        #                   'main
        # at_exit, flag = 1
        # [oops; continuation 0x81d7128 of type 25 is trying to jump from 
runloop 2 to runloop 1]
        # in outer handler
        # in outer handler
        # '
        #     doesn't match '/^main
        # at_exit, flag = 1
        # No exception handler/
        # '

The "in outer handler" line is printed just before the ".end" in the
main routine.  Therefore, the fact that it is printed twice suggests to
me that it must be getting executed once in the inner runloop, and again
in the outer.

   I assume you are alluding to the fact that Parrot could in fact be
hacked to support transfers to other runloops that are still active.
Until then, it appears that the error is still appropriate for
Exception_Handler objects.

   Re PDD23: It's not implementable the way it is. The main problem is
   of course the already discussed continuation border of C code. E.g.

   <cite> The embedding program
          may choose to handle the exception and continue execution by invoking
          the exception's continuation.
   </cite>

   But there's no way currently to safely invoke the continuation from C
   code and continue at some arbitrary runloop nesting.

But these limitations of the current code.  It could be handled by
something like the "Continuations and inferior runloops" POC I posted
9-Aug-06, or by the more comprehensive Cfunc.pod proposal with which you
followed up.

   In fact, one reason I had for consolidating the stack-unwinding code
right now was to make it easier to reimplement actions based on
"Continuations and inferior runloops" technology.  Is that premature?
Would you object if I did so?

   I also dislike the stated mandantory closure-ish behavior of handlers. The 
   general case seems to be that the handler is a label in the context of the 
   handling call frame. If HLL semantics are different, the handler code can 
   still create and call a closure for handling the exception.

It occurs to me that we can have it both ways.  Imagine (as Allison has
already suggested) that Parrot keeps a list of only handlers that are
active.  Checking the handlers is just a matter of running through the
list and invoking each in turn [1].  Per PDD23, each handler would be a
closure, which would decide whether or not to invoke an associated
continuation.  But we could also put continuations on the handler list
as well.  As long as the calling conventions are compatible, this would
allow Parrot to skip the middleman at no extra cost.

   Better still, it provides full backward compatibility.  The existing
C<push_eh> op that takes a label could create an C<Error_Handler>
continuation just as before, so the existing syntax would continue to
have the same semantics.  A new C<push_eh> op that takes a PMC could
provide either behavior, depending on whether it got a continuation or a
closure/sub.

   And I don't get the rationale of:
   <cite>
          3 Invoke the handler (note: this is still in the thrower's 
            dynamic context).
   </cite>
   To me it seems that this should always be the exception handler's context.

In either case, it is easy for the EH to find things out about the
dynamic context in which it was created.  It is [currently] impossible
for it to find out about the thrower's context unless it is run there.
That could be fixed, of course, but would require more context
switching.

   Just in case it is less than clear, PDD23 splits the existing notion
of "handler" into two:  a closure that is invoked in the C<throw>
context, and the cleanup code in the sub that did the C<push_eh> that
might be invoked if the closure decides to return to it.  So only part
of the existing handler semantics is being "relocated" into the dynamic
context of the thrower (and the simplest part at that).  The bulk of the
CATCH block body stays with the containing block, lexically and
dynamically, as it ought.

   It is probably true that Perl 6 error handling as currently defined
could be implemented either way.  But, FWIW, Common Lisp has additional
features that require handlers to be invoked in the dynamic scope of the
error:

   1.  Code nearer the point where the error is signalled is allowed to
bind dynamic variables that can affect the operation of a handler, maybe
temporarily disabling it.  The spec stipulates that this sort of thing
must work.

   2.  CL has a 'restart' feature that depends on handlers that run in
the error context.  For example:

        (defun compile-foo-without-warnings ()
          (handler-bind ((warning #'(lambda (condition)
                                      (declare (ignore condition))
                                      (invoke-restart 'muffle-warning))))
             (compile 'foo)))

Within the scope of the handler-bind (which is just the call to
compile), the warning condition is bound to a handler that invokes a
restart which suppresses the printing of the warning.  The
muffle-warning restart is typically bound dynamically within the warn
function itself, which signals the condition.  So the handler has to run
in the error context in order to see the restart it wants.

   3.  Effective interactive debugging depends on not unwinding the
stack prematurely.

   Admittedly, Parrot has the advantage of being able to create a
continuation that would return to the error context.  But using such a
continuation to support CL error handling would seem to require some
additional mechanism to install the continuation's dynamic context
temporarily without actually invoking the continuation.  That sounds
messy.

   So I certainly hope we can all have our cake and eat it, too.

                                        -- Bob

[1]  Ignoring the matter of keeping the handler out of its own dynamic
     context when invoked, of course.

Reply via email to