Exception creation and destruction in Parrot
Patrick R. Michaud
pmichaud at pobox.com
Wed Oct 29 01:11:49 UTC 2008
Based on a very long discussion in today's #parrotsketch, I'm
offering the following details on the proper use of the push_eh
and pop_eh opcodes in Parrot. This message also describes how
we plan to remove the "auto-disable" feature of the current
exception handling system, and how that impacts existing and
future PIR code.
Background and basics
---------------------
The C<push_eh> and C<pop_eh> opcodes are used for manipulating
exception handlers in Parrot. Every context has a list of
registered exception handlers, C<push_eh> adds a new
exception handler to the end of that list, while C<pop_eh>
removes a handler from the end of the list. Despite the
choice of "push" and "pop" in the opcode names, the list of
registered exception handlers is not truly a "stack".
The basic pattern for creating a standard try/catch sequence is:
try: # start of try block
push_eh catch # register a handler
... # statements that might throw an exception
goto try_end # go to end of try_block
catch: # start of handler
.get_results ($P0) # retrieve thrown exception
... # statements to do any exception handling
# fall through...
try_end:
pop_eh # remove handler from list
... # continue with code after try/catch
Of course, the physical order of these sections can be changed
around a bit -- e.g., moving the handler code to the end of
the sub and explicitly branching back to the C<pop_eh>. The
point is that every handler registered via C<push_eh> should
be removed with a C<pop_eh> when it's no longer in effect.
All exception handlers registered within a given context scope
(invocation) are automatically removed when the scope is
exited, so it's not necessary to explicitly C<pop_eh> a handler
if we're just going to exit the current scope anyway.
Historical view / auto-disable
------------------------------
In versions of Parrot's exception
handling system prior to 0.7.0, a handler that was invoked
(i.e., caught an exception) would automatically be removed
from the list of handlers in scope. Thus there is some legacy
PIR code that performs a C<pop_eh> only if a handler
was not invoked. In recent versions of Parrot this is
no longer the case -- all exception handlers remain in
handler the list until explicitly removed with C<pop_eh>
or leaving the current context scope.
As a temporary stopgap measure to support legacy PIR,
the current versions of Parrot "disable" exception
handlers once they have been invoked such that they
do not catch any subsequent thrown exceptions. This
emulates the behavior that most legacy code expects.
However, we need to migrate legacy PIR code such that
it explicitly removes handlers that are no longer
active instead of relying on the "auto-disable" feature
to make it appear as though a handler is no longer present.
Exception handlers within loops
-------------------------------
Here's a sample pattern for repeatedly registering and
de-registering an exception handler within a looping construct:
loop_iter:
if <end_of_loop> goto loop_end
push_eh catch # register handler for this iteration
... # statements that might throw exception
goto loop_next # iterate to next element
catch:
.get_results ($P0) # retrieve thrown exception
... # statements to handle exception
# fall through to loop_next
loop_next:
pop_eh # de-register the handler
... # code to progress to next iteration
goto loop_iter # branch to next iteration
loop_end:
In this example, since each loop iteration registers a new
exception handler via C<push_eh>, we should also remove
that handler using C<pop_eh> before progressing to the
next iteration. Failing to do so will result in a very
long list of exception handlers in the current context --
one for each iteration.
Instead of creating, registering, and de-registering a
separate exception handler for each iteration of a loop,
a more efficient mechanism would be to create the handler
once at the beginning of the loop, and de-register it at
the loop exit. This would look something like:
push_eh catch # register handler iterations
loop_iter:
if <end_of_loop> goto loop_end
... # statements that might throw exception
goto loop_next # iterate to next element
catch:
.get_results ($P0) # retrieve thrown exception
... # statements to handle exception
# fall through to loop_next
loop_next:
... # code to progress to next iteration
goto loop_iter # branch to next iteration
loop_end:
pop_eh # de-register the handler
However, the code as written here doesn't work in the 0.8.0
version of Parrot, because handlers are automatically disabled
when invoked (see the "Historical Note" section above).
Once the auto-disable feature is removed, the above will work
properly. Until that time, one can either do the explicit
push/pop of a handler on each iteration, or it can be done with
a single handler by explicitly re-enabling the handler after
each time it is invoked. In Parrot 0.8.0 a handler can be
re-enabled by setting it to zero after it has been invoked:
## create a handler
.local pmc handler
handler = new 'ExceptionHandler'
set_addr handler, catch
push_eh handler # register handler
loop_iter:
if <end_of_loop> goto loop_end
... # statements that might throw exception
goto loop_next # iterate to next element
catch:
.get_results ($P0) # retrieve thrown exception
... # statements to handle exception
set handler, 0 # re-enable handler for next iteration
# fall through to loop_next
loop_next:
... # code to progress to next iteration
goto loop_iter # branch to next iteration
loop_end:
pop_eh # de-register the handler
Where we go from here
---------------------
Over the next few days I plan to update legacy PIR code in Parrot
to use the correct push_eh/pop_eh patterns as described above.
We can also officially deprecate the auto-disable feature of exception
handlers so that it can be removed in future releases of Parrot.
Hope this helps,
Pm
More information about the parrot-dev
mailing list