Bug in Jitter

Godmar Back gback at cs.utah.edu
Tue Jul 7 09:34:01 PDT 1998

> > I suspect the proper fix would be to ensure that all live
> > registers are written to memory whenever an exception, including a
> > null pointer exception, may occur.  
> I seem to recall Tim discussing the issue a long while back, also in
> the context of a problem related to finally {} blocks (at that time,
> the test case showed a local var that got clobbered after the
> execution of the finally), and pointing out the trade-off between the
> complexity of flow analysis needed to write back the proper registers
> (and only those) and the performance cost of writing back all live
> registers at every instruction that might cause an exception.

 You're right, I just checked the archives.
Can one resurrect a thread that died out a year ago?

Tim wrote about it over a year ago:

    Tim Wilkinson (tim at tjwassoc.co.uk)
    Wed, 30 Apr 1997 22:58:47 +0100 

    Re: A bug with automatic variables and finally


    This is a JIT related bug. Essentially the value of 'i' is in a
    register when the exception occurs (SEGV in this case) and is lost by
    the time the relevant handler is found. The solution to this problem is
    unclear. The simplest thing to do is to make sure registers are always
    written back to memory before executing instructions which may cause
    runtime exceptions. You can then get cleverer by only writing back
    values which might be needed in the relevant handlers (rather than
    blindly writing then all) for the exception block you're currently in. 
    Even so, this is going to hurt performance for no obvious gain.

I don't want to be a smart-ass, so I leave that to John Ousterhout who
said that that the biggest performance gain is the one from a non-running
to a running program. (*) 

Tim wrote further:

    I had a bit of a wade though my JVM manual and it is remarkably
    unhelpful on how precise exceptions must be. I'd like to believe they
    allow for a 'relaxed' approach to runtime exceptions ... but I should be
    so lucky. Unfortunately, the flow analyses to do this correctly *and*
    efficiently is non-trivial (sigh).

I think the VM Spec, section 2.15.2 might give some advice here.
It says:

    Most exceptions in Java occur synchronously as a result of an action 
    by the thread in which they occur, and at a point in the Java program 
    that is specified to possibly result in such an exception. An 
    asynchronous exception is, by contrast, an exception that can potentially 
    occur at any point in the execution of a Java program.
    Java permits a small but bounded amount of execution to occur before 
    an asynchronous exception is thrown. 
    All exceptions in Java are precise: when the transfer of control 
    takes place, all effects of the statements executed and expressions 
    evaluated before the point from which the exception is thrown must 
    appear to have taken place.

I interpret this such that it is okay to deliver asynchronous exceptions 
at some opportune point---but they must be precise *when* you decide to
deliver them.  (Clearly, we don't do that, and implementing that for
asynchronous exceptions is currently off the horizon).

In the discussion a year ago, the issue seemed to somewhat intermingle both
synchronous and asynchronous exceptions.  I believe that the (possible)
impracticability of correctly implementing asynchronous exceptions should
not deter us from correctly implementing synchronous exceptions.
Besides, asynchronous exception (only Thread.stop() and InternalVMError
can cause those) are on their way out anyway.

I also think that the example by Gary Howland a year ago, where a local
variable that contained a value was clobbered might not be as frequent
as the example encountered by Laurent in the Java WebServer, where the
local variable in question contained a return address.

I also note that at least in the example I sent earlier, l4 is written
to memory anyway, albeit too late.  I think that might be true for most
cases in which functions are invoked.  Simply changing the order of a
few instructions would have fixed that problem.  Of course, the issue
is different for the put/get instructions.

So, I guess the question is:

* is there a way to do a quick and dirty fix that handles most cases?
  (like moving the store of l4 up in the "ft" example).
  Or treating return address register specially?

* do we need to save all registers all the time a synchronous exception
  can happen (at invoke*, get*, put*).

* are we able to do the analysis that is necessary to only flush live 
  registers (which are needed in the exception handler).

	- Godmar

(*) I've been thinking about why people might program like this.
I know there is a school of thinking that says to use exceptions to
detect abnormal conditions, possibly speeding up the normal case.
Consider this example:

	try {
		return p.method();
	} catch (NullPointerException e) {
		return null;

instead of

	if (p == null)
		return null;
	return p.method();

The first example will always try to call p.method, which succeeds in the
common case.  The second example will always take a hit by doing an
additional check, and only be faster in the (rare) case where p is null.

Of course, there is no saying as to where this hoped for gain will go
once you take the preciseness requirements for synchronous exceptions
into account...

More information about the kaffe mailing list