November 2008 Archives

Exceptions in Parrot

| 92 Comments | No TrackBacks

So, let’s talk about exceptions in Parrot.

All of the code in this blog entry is in PIR (Parrot Intermediate Representation, Parrot’s equivalent of ASM).

Throwing an exception from Parrot is pretty straightforward:

.sub main :main
    $P0 = new 'Exception'
    throw $P0
.end

If you want a message on the exception, a severity, etc, it’s like this:

.sub main :main
    $P0 = new 'Exception'
    $P0 = "I couldn't foo. :("
    $P0['severity'] = .EXCEPT_ERROR
    throw $P0
.end

Catching an exception isn’t much harder:

.sub main :main
    push_eh handler
    function_that_could_fail()
    pop_eh
    .return (1)
  handler:
    .get_results ($P0)
    say "Couldn't foo because some function failed"
    .return (0)
.end

That function just returns failure if an exception was thrown between ‘push_eh’ and ‘pop_eh’. Pretty straightforward.

If you wanted to actually recover from the exception and resume continuation, we can do this too. Parrot adds a continuation to the exception object that you can invoke to resume execution at the next opcode after you threw the exception:

.sub main :main
    push_eh handler
    maybe_fail()
    pop_eh
    .return (1)
  handler:
    .local pmc exception, continuation
    .get_results (exception)
    cleanup_and_recover()
    continuation = exception['resume']
    continuation()
.end

There’s a potential problem here with what happens when something in the exception handler, such as the ‘cleanupandrecover’ function we imagined here, throws an exception. When this exceptions system was first implemented, the handler would just catch the exception again, the handler would misbehave again, infinite loop. Bad.

The solution that has been in place for quite a while is to mark the exception handler as “already used” and then to have exception handlers refuse to handle anything if they had already been used. This doesn’t play nicely with resumable exceptions. For example, this code:

.sub main :main
    .local pmc e
    push_eh handler
    e = new 'Exception'
    e = "First Exception"
    throw e
    e = new 'Exception'
    e = "Second Exception"
    throw e
    pop_eh
    say "Everything was fine."
    .return ()
  handler:
    .local pmc ex, co
    .get_results (ex)
    print "Caught exception with message: "
    say ex
    co = ex['resume']
    co()
.end

Currently produces this output:

[sweeks@kweh parrot]$ ./parrot exception-demo.pir
Caught exception with message: First Exception
Second Exception
current instr.: 'main' pc 16 (exception-demo.pir:9)

Parrot can’t find a valid handler for the exception, so it prints the message and the file/line-number it was thrown on and dies.

The currently-used workaround for this is to make an actual ExceptionHandler object and manually mark the exception handler as unused before invoking the resume continuation. This is ugly.

.sub main :main
    .local pmc eh, e
    eh = new 'ExceptionHandler'
    set_addr eh, handler
    push_eh eh
    e = new 'Exception'
    e = "First Exception"
    throw e
    e = new 'Exception'
    e = "Second Exception"
    throw e
    say "Everything was fine."
    pop_eh
    .return ()
  handler:
    .local pmc ex, co
    .get_results (ex)
    print "Caught exception with message: "
    say ex
    eh = 0
    co = ex['resume']
    co()
.end

This works, though:

[sweeks@kweh parrot]$ ./parrot exception-demo.pir
Caught exception with message: First Exception
Caught exception with message: Second Exception
Everything was fine.

To deal with this ugliness, I added the ability to set a filter for exception types or severities on exception handlers a while back. That works like the following:

.include 'include/except_severity.pasm'
.sub main :main
    .local pmc eh, e
    eh = new 'ExceptionHandler'
    set_addr eh, handler
    eh.'min_severity'(.EXCEPT_NORMAL)
    eh.'max_severity'(.EXCEPT_WARNING)
    push_eh eh
    e = new 'Exception'
    e = "First Exception"
    e['severity'] = .EXCEPT_NORMAL
    throw e
    e = new 'Exception'
    e = "Second Exception"
    e['severity'] = .EXCEPT_WARNING
    throw e
    say "Everything was fine."
    pop_eh
    .return ()
  handler:
    .local pmc ex, co
    .get_results (ex)
    print "Caught a warning: "
    say ex
    co = ex['resume']
    co()
.end

That example will only work on parrot r32783 or newer, due to a small bug I just fixed.

So, this ‘disable exception handler’ behavior is very simple to remove. We’ve left it in so long because there are still a few tests that rely on the old behavior. We’re looking to remove it soon, though.

That’s about it for the basics of using exceptions in Parrot. Soon I’ll post about the bug in Parrot that made resuming to another subroutine not work.

About this Archive

This page is an archive of entries from November 2008 listed from newest to oldest.

December 2008 is the next archive.

Find recent content on the main index or look in the archives to find all content.

Pages

OpenID accepted here Learn more about OpenID
Powered by Movable Type 4.32-en