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.