elimination, etc. since the C compiler is not aware that the
translated C code is from Java bytecode.
3. Stack Cutting based on setjmp()/longjmp()
The Java programming language provides an exception
handling mechanism for elegant error handling [2]. When an
error occurs during execution of code in a try block, the error is
caught and handled by an exception handler located in one of
subsequent catch blocks associated with it. If no catch block in
the method can handle the exception, the method is terminated
abnormally and the JVM searches backward through the call
stack to find a catch block which can handle the error. This
mechanism of searching the call stack is called stack
unwinding[14] in CVM.
Since an exception would be an “exceptional” event,
exception handling should be implemented in a way that normal
execution is affected as little as possible. Stack unwinding in the
interpreter mode of the CVM is certainly one such an
implementation. In a b-to-c AOTC, however, stack unwinding is
hard to implement since it is difficult in C to make a direct
control transfer from the excepting point to the exception
handler, if they are located in different methods on the call stack.
One possible solution is using setjmp()/longjmp() C libraries
[16,17].
The setjmp() function saves the current environment at a
jmpbuf-type object for later use by the longjmp(). The
longjmp() function with a jmpbuf-type object argument
restores the environment saved by the last call of setjmp() which
created the object. After the longjmp() completes, program
execution continues as if the corresponding call to setjmp() had
just returned [17]. This is perfect for implementing cross-method
jumps in C.
In fact, all previous b-to-C AOTCs employed a technique
called stack cutting using setjmp()/longjmp() functions. The idea
is that a setjmp() is executed at every method that has a catch
block, and the jmpbuf-type object created by the setjmp() is
pushed on a global stack of jmpbuf-type objects. Later, if a
method has an uncaught exception, a longjmp() is executed with
the top object of the global stack, which will transfer the control
to the method that created the top object. If the exception cannot
be handled in that method, the global stack is popped and a
longjmp() is executed again with a new top object. This process
repeats until the exception handler is found. In normal execution
when no exception occurs within the scope of a method where a
setjmp() is executed, the top object of the global stack is popped
before the function returns since the method cannot be a target of
a longjmp() any more.
There are other works to do for restoring the environment
when returned to the setjmp(). One is restoring the Java stack
frame of the returned method. The other is releasing locks from
all synchronized methods located between the longjmp() method
and the setjmp() method on the call stack. In order to implement
this, we need to maintain another global stack of locked objects
such that all synchronized methods are required to push their
locked object on the global stack at the beginning and pop it at
the end (even if there is no try block in the method). When
returned to the setjmp() of a synchronized method, we pop all
locked objects from the stack until this method’s locked object is
exposed. Then, we release all of popped objects’ locks.
After restoring the environment at the setjmp(), we need to
jump to an appropriate catch block. There have been two
approaches depending on where to place the setjmp(), especially
if there are multiple try blocks in a method. One is placing a
single setjmp() at the beginning of a method. The other is
placing a setjmp() at the beginning of each try block. The
setjmp()-per-method approach is advantageous if more than one
try block is executed in the method since the setjmp() will be
executed only once, whereas multiple setjmp()s will be executed
with the setjmp()-per-try approach (also the two global stacks
are pushed/popped at the beginning/end of each try block).
However, when returning from longjmp(), setjmp()-per-method
requires finding which try block caused the exception before
searching for the catch block associated with it. In order to help
this, we should save the bytecode PC before making a method
call within a try block, which will be used later to consult the
exception table for finding appropriate catch blocks to jump to.
For setjmp()-per-try, this is not necessary since we know which
catch blocks are associated with each try block. The
setjmp()-per-try will be advantageous if no try blocks are
executed in a method. Figure 3 depicts some part of simplified
pseudo code for stack cutting.
One more overhead of stack cutting is that for a method that
has a try block with a method call in it we need to keep all local
C variables in memory, not in registers, by declaring them
volatile. Since longjmp() restores register values as was when
setjmp() was executed, if a local variable is allocated to a
register and if it is changed after setjmp() is executed, longjmp()
will incorrectly restore its previous value. Stack C variables are
exempted from this requirement and can be allocated to registers
since they keep only temporary values and thus are not used
after setjmp().
The setjmp()-per-method approach was employed in [4,6],
while the setjmp()-per-try approach was employed in [5], but
there have been no evaluation of both techniques. In this paper,
we perform such an evaluation for them along with our proposed
technique. We implemented stack cutting as efficiently as
possible to make a fair comparison. For example,
z We place setjmp() and its related code in Figure 3 only
when there is a method call within a try block. If there is no
method call, there is no need for placing a setjmp().
z Instead of saving bytecode PC in setjmp()-per-method, we
save the label of the first catch block associated with a try
block such that when returned from longjmp(), we jump to
the first catch block directly instead of consulting with an
exception table (this works for gcc only). The catch block
will test the exception type and jump to the next one if not
matched.
z When there is an exception in a method and if it can be
handled there, the search process for the catch block is the
same as in our proposed technique which is based on
sequential search.
190