Exception handling and stack clearing headaches

6 views
Skip to first unread message

Charles Oliver Nutter

unread,
Sep 11, 2007, 5:11:08 PM9/11/07
to JVM Languages
I'm having some trouble reconciling Java's exception-handling
mechanism with non-local flow control I need to manage. For example:

def foo
[1,2,3].each {|x| return 'found it' if x == 2}
end

In Ruby, the return here acts as though it were in the body of the
surrounding method, to allow non-local returns to propagate out and
act like a normal embedded block of code. As you might expect, this is
implemented with exceptions. A return from a closure generates a
ReturnJump runtime exception that holds the return value and some
indicator of target (in this case a pointer to the original method's
frame). Once the exception reaches the original method, and it can be
tested that the return target is == current frame, the exception is
unwrapped and the value is returned.

This is all well and good, but it gets more complicated if we have
list iterations that want to terminate early, returning the
appropriate result:

def foo
puts(1, [1,2,3].each {|b| break 'found it' if b == 2}, 3)
end

Here we've got the call to each (with a closure) embedded in the
argument list of a call to "puts". Normal method invocation is fairly
simple: determine the receiver, in this case the "self" object;
process the arguments one by one inserting them into an IRubyObject[]
(or pushing them on a stack if we can support the exact arity); call
the method with the arguments.

However for many calls, we may need to do some exception handling as
part of the call itself to handle non-local flow control that may
bubble out. And if this handling happens in the midst of argument
processing, we end up branching to exception-handling logic with our
receiver and all our previous arguments wiped out because the stack
clears.

So I guess my question is this: how can we incorporate exception
handling-as-flow-control into generated bytecode while still keeping
most operands on the stack? Save and reload stack state? Something
else? In general, is there a strategy for compiling try/finally with
many possible branches inside the try (i.e. how do you structure a
compiler to appropriately and efficiently inline finally blocks?).

Am I the only one coping with such issues?

- Charlie

David Pollak

unread,
Sep 11, 2007, 6:26:15 PM9/11/07
to jvm-la...@googlegroups.com
I forward this message to the Scala team.  They've got some experience with these issues and may be able to help out.
--
lift, the fast, powerful, easy web framework
http://liftweb.net

Charles Oliver Nutter

unread,
Sep 11, 2007, 6:45:46 PM9/11/07
to jvm-la...@googlegroups.com
Charles Oliver Nutter wrote:
> So I guess my question is this: how can we incorporate exception
> handling-as-flow-control into generated bytecode while still keeping
> most operands on the stack? Save and reload stack state? Something
> else? In general, is there a strategy for compiling try/finally with
> many possible branches inside the try (i.e. how do you structure a
> compiler to appropriately and efficiently inline finally blocks?).

Another example, provided by one of the Ruby core developers:

a = (begin; raise; rescue; :value; end)

In the midst of assignment (or method call), we need to handle an
exception raised and caught and continue along like nothing happened.

- Charlie

Rémi Forax

unread,
Sep 11, 2007, 7:23:27 PM9/11/07
to jvm-la...@googlegroups.com
Charles Oliver Nutter a écrit :
In Java, try/catch block is an instruction, correct me if i'm wrong
but i don't think the bytecode has this limitation.
So if think it's mostly an implementation problem,
perhaps your catch is not at the right place ?

The offsets of the exception handler should immediately surround
the code that iterate on the array, call the closure
and store the results in an array.

In pseudo code, i should be something like that:

ldc 1
try {
for each ...
invoke closure
aiload // load the array, perhaps it is already on stack ?
} catch(ReturnJump) {
pop // pop exception
ldc 'found it'
}
ldc 3
invoke puts

> Something
> else?
John Rose's blog about transfer control
http://blogs.sun.com/jrose/entry/longjumps_considered_inexpensive


> In general, is there a strategy for compiling try/finally with
> many possible branches inside the try (i.e. how do you structure a
> compiler to appropriately and efficiently inline finally blocks?).
>

take a look to javac, it inlines finally by generating the finally code in
each possible branch.


> Am I the only one coping with such issues?
>

at least if Java 7 support closure, javac should cope with such issues :)
> - Charlie
>
Rémi

Charles Oliver Nutter

unread,
Sep 11, 2007, 7:27:06 PM9/11/07
to jvm-la...@googlegroups.com
Rémi Forax wrote:
> ldc 1
> try {
> for each ...
> invoke closure
> aiload // load the array, perhaps it is already on stack ?
> } catch(ReturnJump) {
> pop // pop exception
> ldc 'found it'
> }
> ldc 3
> invoke puts

I was under the impression that the stack was completely cleared.
Perhaps I misunderstood, and only the portion of the stack available
before entering the "try" is cleared, with what existed before "try"
remaining on the stack? If so, then it may simply be a matter of
ratcheting down the scope of the try/catch to surround only the
invocation itself.

I will try to make this change and see if it functions correctly.

- Charlie

Kenneth Russell

unread,
Sep 11, 2007, 8:06:26 PM9/11/07
to jvm-la...@googlegroups.com
Charles Oliver Nutter wrote:
> Rémi Forax wrote:
>> ldc 1
>> try {
>> for each ...
>> invoke closure
>> aiload // load the array, perhaps it is already on stack ?
>> } catch(ReturnJump) {
>> pop // pop exception
>> ldc 'found it'
>> }
>> ldc 3
>> invoke puts
>
> I was under the impression that the stack was completely cleared.
> Perhaps I misunderstood, and only the portion of the stack available
> before entering the "try" is cleared, with what existed before "try"
> remaining on the stack? If so, then it may simply be a matter of
> ratcheting down the scope of the try/catch to surround only the
> invocation itself.

The entire operand stack for the current activation is cleared when an
exception is thrown in this activation or rethrown from a callee
activation. You're right, there is complexity involved here, possibly
involving saving operand stack values off into local variables and
restoring them later.

-Ken

Charles Oliver Nutter

unread,
Sep 11, 2007, 8:28:28 PM9/11/07
to jvm-la...@googlegroups.com

Perhaps a definition of "activation" would be useful here :)

- Charlie

Kenneth Russell

unread,
Sep 11, 2007, 8:32:41 PM9/11/07
to jvm-la...@googlegroups.com

The frame corresponding to the currently executing Java method, as
defined by the Java Virtual Machine Specification.

-Ken

John Wilson

unread,
Sep 12, 2007, 3:27:08 AM9/12/07
to jvm-la...@googlegroups.com
On 9/11/07, Charles Oliver Nutter <hea...@gmail.com> wrote:
> This is all well and good, but it gets more complicated if we have
> list iterations that want to terminate early, returning the
> appropriate result:
>
> def foo
> puts(1, [1,2,3].each {|b| break 'found it' if b == 2}, 3)
> end
>
> Here we've got the call to each (with a closure) embedded in the
> argument list of a call to "puts". Normal method invocation is fairly
> simple: determine the receiver, in this case the "self" object;
> process the arguments one by one inserting them into an IRubyObject[]
> (or pushing them on a stack if we can support the exact arity); call
> the method with the arguments.


One possibility would be to put "[1,2,3].each {|b| break 'found it' if
b == 2}" into a synthetic method on the class with the try catch logic
in that method. Hotspot should inline the method so there should be no
major performance hit.

John Wilson

Charles Oliver Nutter

unread,
Sep 12, 2007, 3:38:46 AM9/12/07
to jvm-la...@googlegroups.com
John Wilson wrote:
> One possibility would be to put "[1,2,3].each {|b| break 'found it' if
> b == 2}" into a synthetic method on the class with the try catch logic
> in that method. Hotspot should inline the method so there should be no
> major performance hit.

That's the approach we've been going with, and probably the way I'm
going to handle most of these issues. Since I *can* tell ahead of time
whether a subtree of the AST involves exception-handling, I can spin it
off to another method call fairly easily.

- Charlie

David Pollak

unread,
Sep 12, 2007, 9:57:56 AM9/12/07
to jvm-la...@googlegroups.com


---------- Forwarded message ----------
From: Iulian Dragos <iulian...@epfl.ch>
Date: Sep 12, 2007 6:25 AM
Subject: Re: Fwd: Exception handling and stack clearing headaches
To: Lex Spoon <l...@lexspoon.org>
Cc: David Pollak < feeder.of...@gmail.com>, martin....@epfl.ch, Burak Emir <burak...@gmail.com>

Hello,

In Scala we have a similar problem when try-catch blocks of non-unit
typer are used in expressions:

def foo = {
   1 + (try { readInt } catch { case e: IOException => 0 })
}

As you have noted, the exception thrown by readInt cleans the stack, and
loses the '1' that's been already loaded. Our solution is to lift these
expressions into synthetic methods:

     def foo(): Int = 1.+({
       Foo.this.liftedTry0$0()
     });

     private def liftedTry0$0(): Int = try {
       scala.this.Predef.readInt()
     } catch {
       case e: java.io.IOException => 0
     }

They are reasonably rare, so there is usually no bloat.


>> So I guess my question is this: how can we incorporate exception
>> handling-as-flow-control into generated bytecode while still keeping
>> most operands on the stack? Save and reload stack state? Something

I am not sure about the architecture of the JRuby compiler, but maybe an
early pass could determine problematic cases and either lift them as we
do, or transform the method to some 'normal' form where the stack is not
used (for instance, all arguments to method calls are identifiers).


>> else? In general, is there a strategy for compiling try/finally with
>> many possible branches inside the try (i.e. how do you structure a
>> compiler to appropriately and efficiently inline finally blocks?).

I am not sure I understand the last part. In Scala finalizers are indeed
inlined (both in the block, and in the 'catches').


>> Am I the only one coping with such issues?

Definitely not.

Iulian

Rémi Forax

unread,
Sep 12, 2007, 10:24:41 AM9/12/07
to jvm-la...@googlegroups.com
Charles Oliver Nutter a écrit :
> Rémi Forax wrote:
>
>> ldc 1
>> try {
>> for each ...
>> invoke closure
>> aiload // load the array, perhaps it is already on stack ?
>> } catch(ReturnJump) {
>> pop // pop exception
>> ldc 'found it'
>> }
>> ldc 3
>> invoke puts
>>
>
> I was under the impression that the stack was completely cleared.
>
you are right, i've learnt something today :)

> Perhaps I misunderstood, and only the portion of the stack available
> before entering the "try" is cleared, with what existed before "try"
> remaining on the stack? If so, then it may simply be a matter of
> ratcheting down the scope of the try/catch to surround only the
> invocation itself.
>
> I will try to make this change and see if it functions correctly.
>
> - Charlie
>
Rémi

Charles Oliver Nutter

unread,
Sep 12, 2007, 11:47:32 AM9/12/07
to jvm-la...@googlegroups.com
> In Scala we have a similar problem when try-catch blocks of non-unit
> typer are used in expressions:
>
> def foo = {
> 1 + (try { readInt } catch { case e: IOException => 0 })
> }
>
> As you have noted, the exception thrown by readInt cleans the stack, and
> loses the '1' that's been already loaded. Our solution is to lift these
> expressions into synthetic methods:

Ok, it sounds like the synthetic method strategy is winning out. And
yes, these scenarios are rare in Ruby as well, so I think the extra
overhead is no problem.

> >> So I guess my question is this: how can we incorporate exception
> >> handling-as-flow-control into generated bytecode while still keeping
> >> most operands on the stack? Save and reload stack state? Something
>
> I am not sure about the architecture of the JRuby compiler, but maybe an
> early pass could determine problematic cases and either lift them as we
> do, or transform the method to some 'normal' form where the stack is not
> used (for instance, all arguments to method calls are identifiers).

There is an early pass immediately before compilation, which currently
is just used to determine the scoping characteristics of a given method
(do we need a heap-allocated scope or can we use stack-based locals).
Along with this pass it would be trivial to set additional flags for
such details.

- Charlie

Per Bothner

unread,
Sep 12, 2007, 3:56:32 PM9/12/07
to jvm-la...@googlegroups.com
Kenneth Russell wrote:
> The entire operand stack for the current activation is cleared when an
> exception is thrown in this activation or rethrown from a callee
> activation. You're right, there is complexity involved here, possibly
> involving saving operand stack values off into local variables and
> restoring them later.

The gnu.bytecode framework used by Kawa does this automatically,
In Kawa try-catch is an expression form.

--
--Per Bothner
p...@bothner.com http://per.bothner.com/

Per Bothner

unread,
Sep 12, 2007, 4:12:53 PM9/12/07
to jvm-la...@googlegroups.com
John Wilson wrote:
> One possibility would be to put "[1,2,3].each {|b| break 'found it' if
> b == 2}" into a synthetic method on the class with the try catch logic
> in that method. Hotspot should inline the method so there should be no
> major performance hit.

The hit comes if you have to create a closure object of some kind,
because the try/catch/finally references a variable outside the
try/catch/finally. (I'm assuming lexical scoping.) I doubt
HotSpot can optimize that away. If the variable isn't assigned to
in the try/catch/finally (or any closure that might be called by
them), then you can probably pass the variable as an extra
parameter.

Saving the stack in local variables seems a better solution.

John Wilson

unread,
Sep 12, 2007, 4:47:27 PM9/12/07
to jvm-la...@googlegroups.com

I don't know enough about the details of the Ruby semantics, but in
Groovy and Ng the thing between { and } is a closure and any local
variables referenced would have already been migrated to the heap. So
in these two cases there would be no extra overhead.

Charles can you help us out here?

John Wilson

Charles Oliver Nutter

unread,
Sep 12, 2007, 9:10:19 PM9/12/07
to jvm-la...@googlegroups.com
John Wilson wrote:
> I don't know enough about the details of the Ruby semantics, but in
> Groovy and Ng the thing between { and } is a closure and any local
> variables referenced would have already been migrated to the heap. So
> in these two cases there would be no extra overhead.
>
> Charles can you help us out here?

At the moment, JRuby will only use stack-based (or is it
"register-based?") locals when there's no closure present. Any closure
present *at all* will force the method to compile using a heap-based
scope instead. This is because any closure can also be repurposed as a
binding to an eval call somewhere else in the system, and it will need
to have access to all local variables, even those not directly
referenced from within that closure:

def foo
a = 1
bar {} # note 'a' is not referenced
end

def bar(&block)
eval "puts a", block
end

foo # => "1"

I presume the feature is present for DSL purposes...so that you don't
have to pass BOTH a binding and a closure, since the closure really has
everything the binding would. Nevertheless, it means any closure present
in a method implies I need to use a heap-based scope.

Of course we also require heap-based locals for "eval" among other
dangerous methods.

In the case that I compile embedded try/catch as a synthetic method, I
will likely pass along the heap-locals structure along with other bits
and pieces needed to execute the method.

I'm also considering the possibility of optionally disabling the binding
behavior and using "variable buckets" like Groovy and XRuby, but I'm not
yet convinced that constructing n variable buckets is going to be
faster/less overhead than constructing a single heap-variable store of
size n. I also have to consider the constant presence of eval, since it
wants access to an entire heap-based scope...so buckets may never really
be applicable.

- Charlie

Iulian Dragos

unread,
Sep 13, 2007, 4:41:51 AM9/13/07
to JVM Languages
Hello,

In Scala we have a similar problem when try-catch blocks of non-unit
typer are used in expressions:

def foo = {
1 + (try { readInt } catch { case e: IOException => 0 })
}

As you have noted, the exception thrown by readInt cleans the stack,

and loses the '1' that's already been loaded. Our solution is to lift


these expressions into synthetic methods:

def foo(): Int = 1.+({
Foo.this.liftedTry0$0()
});

private def liftedTry0$0(): Int = try {
scala.this.Predef.readInt()
} catch {
case e: java.io.IOException => 0
}

They are reasonably rare, so there is usually no bloat.

>> So I guess my question is this: how can we incorporate exception


>> handling-as-flow-control into generated bytecode while still keeping
>> most operands on the stack? Save and reload stack state? Something

I am not sure about the architecture of the JRuby compiler, but maybe


an early pass could determine problematic cases and either lift them
as we do, or transform the method to some 'normal' form where the
stack is not used (for instance, all arguments to method calls are
identifiers).

>> else? In general, is there a strategy for compiling try/finally with


>> many possible branches inside the try (i.e. how do you structure a
>> compiler to appropriately and efficiently inline finally blocks?).

I am not sure I understand the last part. In Scala finalizers are


indeed inlined (both in the block, and in the 'catches').

>> Am I the only one coping with such issues?

Definitely not.

Iulian

Charles Oliver Nutter

unread,
Sep 13, 2007, 4:58:35 AM9/13/07
to jvm-la...@googlegroups.com
...reposting my reply, since Iulian's post arrived:

> > In Scala we have a similar problem when try-catch blocks of non-unit
> > typer are used in expressions:
> >
> > def foo = {
> > 1 + (try { readInt } catch { case e: IOException => 0 })
> > }
> >
> > As you have noted, the exception thrown by readInt cleans the
stack, and

> > loses the '1' that's been already loaded. Our solution is to lift these
> > expressions into synthetic methods:

Ok, it sounds like the synthetic method strategy is winning out. And


yes, these scenarios are rare in Ruby as well, so I think the extra
overhead is no problem.

> > >> So I guess my question is this: how can we incorporate exception


> > >> handling-as-flow-control into generated bytecode while still
keeping
> > >> most operands on the stack? Save and reload stack state? Something
> >
> > I am not sure about the architecture of the JRuby compiler, but
maybe an
> > early pass could determine problematic cases and either lift them as we
> > do, or transform the method to some 'normal' form where the stack
is not
> > used (for instance, all arguments to method calls are identifiers).

There is an early pass immediately before compilation, which currently

Reply all
Reply to author
Forward
0 new messages