Now that invocation semantics are fixed and permanent, we need some cleanup of the code base to round out support. It's also time to get some things in place that are part of the fallout from it.

The 'invoke the current return continuation' op apparently got lost in the blowup. That needs to go in. "return" is a simple, if unimaginative name, and that works for me.

The wording of PDD 03 implies that some of the registers can be populated by the op making the invocation, rather than explicitly setting them. In this case specifically the method/sub PMC, the object, and the return continuation. Leo brought this up but it got lost in the firestorm. We've already got "invoke Px" and "invokecc Px" along with the plain invoke and invokecc, but method calls aren't as well-rounded, and tail-call versions would be useful as well.

With that out of the way, we should address exceptions.

There's some support in now, but it's primitive, and needs updating.

Exceptions are fairly straightforward. Setting an exception handler creates a exception continuation and pushes it onto the control stack. If an exception is thrown the control stack is walked looking for an exception handler and, when one is found, its continuation is invoked, at which point we transfer control to the exception handler.

The following parameters are passed into the handler (and this I'm up for discussion on):

  *) Language throwing exception
  *) The subsystem throwing the exception
  *) The exception severity
  *) The exception code
  *) An object which the exception handler can do whatever it wants with

Exceptions are not, by default, resumable. That's problematic with some classes of exception, so we aren't doing that. (Not, mind, that you can't throw a continuation to resume to as part of your object you throw at the exception handler)

I'd like pushing exception handlers to remain simple -- the current system is almost OK. What I'd like it to change to is:

    push_eh label

with popping the top exception handler being:

    pop_eh

I'm up for better names, too. When pushing an exception handler we generate a return continuation with the resume address being the label rather than the next op.

This is more restrictive than passing in a generic invokable object as the exception handler, but given that all the exception handling schemes in all our languages of interest do lexically scoped exceptions I think we're better off with the restriction. Allowing random invokables as exception handlers feels like it's likely to have some subtle security or consistency problems associated with it, though I can't give a good example at the moment. (Plus it can be overridden by a language that actually cares by having the exception handler invoke a random continuation) Still, forcing all exception objects to be created by parrot gives us some guarantees of control, so that's fine.

Throwing exceptions should be:

   throw language, subsystem, severity, code, object

And I'm definitely unsure what types language and subsystem should be. (I can see either ints or strings here) severity and code should both be ints, and object is a PMC.

Rethrowing the current exception status should be done with the rethrow op, which takes no parameters.

As part of this I want to reserve one language identifier for parrot-internal exceptions which can *not* be thrown by bytecode. The only way for these to propagate is if C code throws them or they're rethrown by an exception handler which can't handle them. This'll be used for exceptions we don't want to be fakeable. (I'm not sure we'll need any, but if we do I want the option) If bytecode does throw them it becomes a security exception instead, and we may well up and kill the interpreter.

With pushing and popping exception handlers, we're going to want to pop off several exception handlers at once, in some cases an unknown number of handlers. As such I want to reintroduce something we talked about a very long time ago: stack marks. This allows us to do:

    pushmark 12
    .
    . random code here
    .
    popmark 12

where everything put on the control stack after the "pushmark identifier" is popped off by the corresponding "popmark identifier". Marks can nest, and it's the code's responsibility to not reuse labels. popmark will *not* cross routine boundaries, so the control stack will never be cleared out past the point where it was when the sub/method/whatever was entered. (Note that exception handlers don't count as subs here) That can be an exception in itself if we want, and probably ought to do.

Now, with this, there's one other thing we need to talk about, and that's scope exit actions. We promised this a number of years back (and now I feel very old) and need to deliver on them. A scope exit action is put in place on the control stack with:

   pushaction Psub

and when the action is popped off the stack because of a popmark, walking the stack back looking for exception handlers, a popaction op, or returning from a subroutine the sub is invoked with a *single* integer parameter that indicates whether the action is being called as part of a normal or exception-based stack unwind. (a status of 0 means the action was caused by normal code, a 1 because the action was popped off as parrot walked backwards looking for an exception handler)

If folks want to argue that the action should get the exception information if called as part of an exception unwind I'm OK with that. Go for it, I'm unsure of that one.

Most of the stack unwinding stuff's straightforward -- it's called because code actually *did* something to the stack, or pitched a fit. The one place we need to do some stuff with is on subroutine exit.

With CPS we don't truly have subroutine exits, since we just invoke continuations and bop around. There's no 'exit' as such. And, of course, nobody actually *cares* that we do this -- our convenience isn't really that interesting. That means we need to simulate exiting under normal circumstances. So, parrot will automatically clear out the control stack and invoke any actions on it if:

   *) the current return continuation is invoked with the return op
   *) a tail call is made with either the tailcall or tailmethodcall ops

Note that the unwinding is *not* inherent in the invocation of the return continuation! That means that if someone fetches the return continuation out of the interpreter structure and then explicitly invokes it the control stack isn't walked for action handlers. (Which would give a quick escape if code wanted to skip them for some reason, though we'd probably be better off with variant return/tailcall ops instead since it'd be cheaper than fully instantiating the return continuation)

It's also important for people writing these things to take into account the possibility that their exit actions may potentially be triggered multiple times, courtesy of the joys of continuations.

So. Simple, right? Make sense to everyone? (You may all commence ripping this to shreds...)
--
Dan


--------------------------------------it's like this-------------------
Dan Sugalski                          even samurai
[EMAIL PROTECTED]                         have teddy bears and even
                                      teddy bears get drunk

Reply via email to