ExceptionHandlers aren't (yet) share-able

0 views
Skip to first unread message

Patrick R. Michaud

unread,
Dec 18, 2009, 4:58:21 PM12/18/09
to parro...@lists.parrot.org
For the past couple of weeks, chromatic and I have been exploring
various mechanisms to create a single ExceptionHandler instance
for a Sub that can be shared across multiple invocations of that Sub.

Looks like that approach is not going to work at all -- at least
not with the current ExceptionHandler implementation. The long
demonstration of this is below, but the short answer is that
ExceptionHandler PMCs are actually Continuations, and thus
the instance of the ExceptionHandler PMC is tied to the
context in which it was created, and when invoked returns to
that context.

To demonstrate this, I'll use the following NQP code:

$ cat fib-nqp-return.nqp

sub fib($n) {
return ($n < 2) ?? $n !! fib($n-1) + fib($n-2);
}

my @ARGS := pir::getinterp__P()[2];
my $N := @ARGS[1] || 28;

Q:PIR { time $N0 };
my $r := fib($N);
Q:PIR {
time $N1
$N1 -= $N0
};
pir::say("fib($N) = $r " ~ Q:PIR { %r = box $N1 });

Converting the above to PIR and running it works fine, albeit
a bit slowly (the source files used for this are attached to this
message):

$ ./parrot-nqp --target=pir fib-nqp-return.nqp >fib-nqp-return-1.pir
$ ./parrot fib-nqp-return-1.pir
fib(28) = 317811 20.2946200370789

The code that creates the ExceptionHandler for 'fib' is at the
beginning of 'fib' (fib-nqp-return-1.pir:78):

.sub "fib" :subid("11_1261172486.60629") :outer("10_1261172486.60629")
.param pmc param_16
.annotate "line", 1
new $P15, 'ExceptionHandler'
set_addr $P15, control_14
$P15."handle_types"(58)
push_eh $P15
...

We can manually edit this so that we get a single ExceptionHandler
object that is re-used on subsequent invocations -- i.e., the
optimization that chromatic and I have been working towards:

.sub "fib" :subid("11_1261172486.60629") :outer("10_1261172486.60629")
.param pmc param_16
.annotate "line", 1
$P15 = get_global 'fib_eh'
unless null $P15 goto have_fib_eh
new $P15, 'ExceptionHandler'
set_addr $P15, control_14
$P15."handle_types"(58)
set_global 'fib_eh', $P15
have_fib_eh:
push_eh $P15
...

Unfortunately, that change causes the code to no longer
work at all:

$ cp fib-nqp-return-1.pir fib-nqp-return-2.pir
$ vi fib-nqp-return-2.pir # make changes here
$ ./parrot fib-nqp-return-2.pir
fib(28) = 1 0.000290870666503906

I'm guessing that when the return exception is thrown, the ExceptionHandler
is invoked and returns to the context in which it was created (skipping
all of the intermediate contexts that attempted to (re)use the
ExceptionHandler object). Thus we recurse 27 times, throw a
return exception with a value of 1, that jumps back to the
first invocation of fib(), and 1 gets returned as our result.

I can envision a few ways in which we might be able to
create shared ExceptionHandler PMCs, but all of them feel
like fairly deep changes to the entire exception (and perhaps
continuation) subsystems, so for now I'm just posting this
analysis and will let other Parrot designers decide where to
take things from here.

Pm

fib-nqp-return.nqp
fib-nqp-return-1.pir
fib-nqp-return-2.pir

Bob Rogers

unread,
Dec 19, 2009, 4:55:07 PM12/19/09
to Patrick R. Michaud, parro...@lists.parrot.org
From: "Patrick R. Michaud" <pmic...@pobox.com>
Date: Fri, 18 Dec 2009 15:58:21 -0600

For the past couple of weeks, chromatic and I have been exploring
various mechanisms to create a single ExceptionHandler instance for a

Sub that can be shared across multiple invocations of that Sub . . .

[examples omitted]

I'm guessing that when the return exception is thrown, the
ExceptionHandler is invoked and returns to the context in which it
was created (skipping all of the intermediate contexts that attempted
to (re)use the ExceptionHandler object). Thus we recurse 27 times,
throw a return exception with a value of 1, that jumps back to the
first invocation of fib(), and 1 gets returned as our result.

Yes. The problem is that "fib" is recursive, so must have multiple
active invocations, each of which needs a separate active handler.

FWIW, for non-recursive functions, this approach ought to work if the
set_addr were done unconditionally:

$P15 = get_global 'fib_eh'
unless null $P15 goto have_fib_eh
new $P15, 'ExceptionHandler'

$P15."handle_types"(58)
set_global 'fib_eh', $P15
have_fib_eh:

set_addr $P15, control_14
push_eh $P15

However, this code returns the same answer when it ought to get stuck in
an unbounded loop, always returning to the *last* invocation. I am
guessing that this is because set_addr has changed since I last looked;
I can't see where the return context is established.

But in any case, to apply this optimization, the compiler would have
to prove that the sub could never have multiple simultaneously active
invocations. This would be difficult at best for a dynamic language,
and a complete nonstarter in the presence of coroutines.

Sorry for not delivering the bad news sooner, but this line of
reasoning didn't occur to me until I read your analysis.

-- Bob Rogers
http://www.rgrjr.com/
_______________________________________________
http://lists.parrot.org/mailman/listinfo/parrot-dev

Reply all
Reply to author
Forward
0 new messages