I've been working on some low hanging fruit optimizations for the code generator and
I came across the following peculiar bytecode pattern used by Jikes to compile synchronized
blocks:
0 goto 6
3 aload_1
4 monitorexit
5 athrow
6 aload_0
7 dup
8 astore_1
9 monitorenter
10 aload_1
11 monitorexit
12 return
Exception table:
start_pc = 3
end_pc = 5
handler_pc = 3
catch_type = java/lang/Throwable
start_pc = 10
end_pc = 12
handler_pc = 3
catch_type = java/lang/Throwable
This code results from this method:
static void main(String[] args) {
synchronized(args) {
}
}
I was confused by the first entry in the exception table. It protects part of the
exception handler and points to itself. Why would you do this?
Luckily, Jikes is open source, so I went and looked at the source code
(see ByteCode::EmitSynchronizedStatement). The comment in the function explains that
the additional protection of the exception handler is there to deal with asynchronous
exceptions. The Jikes bug database contains a better explanation of
the issue.
So, it turns out that this somewhat strange looking construct is actually a perfect
way to make sure that locks are always released (and only once) even if an asynchronous
exception occurs. (Note that this assumes that monitorexit is an atomic instruction,
wrt asynchronous exceptions, this isn't in the JVM specification[1], but it is a reasonable
assumption.)
At the moment, IKVM compiles this code as follows (pseudo code):
object local = args;
object exception = null;
Monitor.Enter(local);
try {
// this is where the body of the synchronized block would be
Monitor.Exit(local);
} catch(System.Exception x) {
exception = x;
ExceptionHelper.MapExceptionFast(x);
goto handler;
}
return;
handler:
try {
Monitor.Exit(local);
} catch(System.Exception x) {
exception = x;
ExceptionHelper.MapExceptionFast(x);
goto handler;
}
throw x;
This is obviously pretty inefficient, but more importantly, it is incorrect. If an
asynchronous exception occurs at the right (or rather, wrong) moment the lock will
be released twice.
The right way to compile this would be (pseudo code again):
object local = args;
Monitor.Enter(local);
try {
// this is where the body of the synchronized block would be
} finally {
Monitor.Exit(local);
}
Of course, in the current version of the CLR, this still wouldn't be safe in the face
of asynchronous exceptions, but Chris Brumme assures us
that in future versions it will be.
BTW, an alternative way to compile it (which, presumably, would work correctly even
in today's CLR), is to move the synchronized block into a new method that is marked
with MethodImplOptions.Synchronized.
The tricky part of both of these solutions, is recognizing the code sequences that
need to be compiled as a try finally clause. Various Java compilers can use different
patterns (although a pretty firm clue is provided by the two exception blocks that
must end exactly after the monitorexit instruction).
This is one situation where compiling bytecode instead of Java source, makes it a
lot harder to do the right thing.
[1] The JVM specification actually contains an incorrect
example of how to compile a synchronized block. Not only does this example not
use the above protection against asynchronous exceptions, it also doesn't protect
the aload_2 and monitorexit instructions at offset 8 and 9.