|Version 4 (modified by whiteknight, 5 years ago)|
- Exception PMC should be completely subclassable from PIR. An Exception Subclass should be able to be passed to throw, die, rethrow, finalize, etc.
- Allow ExceptionHandlers to be able to filter the kinds of Exception it catches by class/role/VTABLE_does/VTABLE_isa/etc
- ExceptionHandler PMC should be completely subclassable from PIR, and usable in all situations where an ExceptionHandler is used
- ExceptionHandler should be allowed to be, or be subclassed with, A Sub-like type. So long as the ExceptionHandler subclass implements VTABLE_invoke and a few other attributes, it should be usable as expected.
- ExceptionHandler.set_pointer is used to set the target address AND the current runloop ID (for use with the finalize opcode). Object.set_pointer falls back to a delegate object, and sets the address/runloop_id on the delegate. In the finalize_p opcode, we do a GETATTR to fetch runloop_id from the Object, which isn't set.
- ExceptionHandler PMC should have an option that forces it to be popped off the handlers list automatically when it is invoked. These kinds of handlers can be used to avoid infinite recursion of handlers throwing exceptions.
- The Embedding API needs an interface for C-based exception handlers. Prefer func pointers instead of jmp_buf, if possible.
- Backtraces should show information about an Exception from the period before a rethrow (TT #1283)
- Create a new BackTrace PMC type that encapsulates a backtrace and provides programmatic access to the contents in a backtrace, and also a preformatted string representation of it.
- BackTrace should also be completely subclassable.
- Simplify attr processing in Exception. The current algorithm to look up attr_ enum values in a long string of if(STRING_equal(...)) is very bad for performance.
Commentary And Issues
The point of exceptions is to enable a whole bunch of 'dynamic' (like scope) flow control.
The PCT stuff uses exceptions for things like return, break, and continue, as well as the conventional "a surprising result has occurred." There is a legitimate need for fast, static handling of different types of control transfers. Typed exceptions definitely are one answer to that need. It is conceivable that more types could be added.
Most programming languages support the idea of exception classes. Further, they support catching exceptions based on a class hierarchy: catch an exception of such-and-such class, or any subclass.
An exception class will likely extend a more basic exception class in the language. It is easily conceivable that exceptions may be implemented as roles, or using multiple inheritance.
So catching a classed exception either looks like "catch all exceptions, then filter by class, then rethrow the ones you don't like", or it looks like "register the classes you catch, some of which may be subclasses of each other, and have the dispatcher run through the 'isa' query to find a match".
Perl is driving the idea of resumable exceptions, I think. (I remember advocating for it on p6l about 80 years ago.) This causes a problem in that a resumable exception doesn't necessarily roll up the stack. A non-resumable exception, which is what the industry is used to, does. So a resumable exception is kind of like the old ON ERR GOSUB directive - it calls a "sub-routine" (maybe) that might possibly return back to the site of the exception (or to some other continuation, blah blah). Or it might never be heard from again.
There's no obvious way to implement finally, or something like it, if an outer exception handler is going to maybe resume the exception. If the exception is non-resumable, you can close those files. If the exception is definitely going to be resumed you should not close the files. But if you don't know, then what?
(The only answer to this that springs immediately to mind is to have a "last" exception handler that converts a resumable to a non-resumable, and throws that. I sure hope somebody smarter than me has thought about this...)
The current typed system fails to handle this approach. It is unlikely that a single-type-per-exception approach will ever perform adequately in the presence of multiple inheritance and role mixins.
However, the current system is arguably fast - I have no data for this, but if it isn't fast then it has no redeeming value. A single numeric lookup deserves to be fast.
I think the priority has to be given to making 'simple things simple.' For exceptions, that means performance for the control flow exceptions, and reliability for die.
Performance, in this case, can mean whatever the internals guys want it to mean. But in general it should probably mean low cost and getting rid of nested interpreters.
Implementation of 'unusual exceptions' should be optimized not so much for speed, but for convenience. That is, getting rid of inferior runloops, or whatever other thing raises its head.
- The non-local flow, catchable exceptions, and fatal error mechanisms must be extensible - more different kinds of each may be added by user code.
- Non-local control flow needs to be fast, for things like 'break', 'continue', 'return'
- Handling of 'errors' needs to be reliable - no matter how bad the user code, a 'die' type error has to be delivered.
- Catchable exceptions should be handled well, but may trade performance for implementation facility.
A cloud over much of this is coroutines. Coroutines, or some other form of random flow, have to support separate "stacks" for control transfer. That is, if a lexical block throws a "loop break" control exception, the right outer block has to receive it. I suspect this may have already been thought through - I certainly hope so.
That aside, control flow exceptions seem like they are really a different animal, piggy-backing on the exception mechanism. That's not wrong, per se, but it may be possible for control flow stuff to move to a different subsystem with better performance.
In particular, control flow is generally "symmetric" - that is, you don't generate a "next" without knowing there's a "for" somewhere to catch it. (In fact, I suspect most languages know where the for loop is, to boot. Does a control flow implicitly have a target?) In this regard, control flow is probably more like 'longjmp' than the rest of the exceptions mechanism - you know where you're going, and there's a good protocol defined between sender and receiver.
Presently, the throw internals are oriented towards a "nested call" instead of towards the "return the address of the next opcode". There are probably reasons for this, but are they good enough?
There has been some pressure to "make exception handlers be subs" (TT#1091), and the counter-pressure was the handling of control exceptions. If there is validity to the runloop problems being tied to continuations (vice subs), this would be a good opportunity for a split.