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