I just finished changing the instrumentor for synchronized blocks. Now it generates bytecode that pretty much exactly matches hand-written Java code. This synchronized block
1 2 3 | synchronized(this) { foo(); } |
with the bytecode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | aload\_0 // load this dup astore\_1 // store in local 1 monitorenter // lock this aload\_0 // load this invokevirtual (foo) // call foo() aload\_1 // restore this from local 1 monitorexit // unlock this goto 18 // jump to return // handler for any exception (lines 5-6) astore\_2 // store exception in local 2 aload\_1 // restore this from local 1 monitorexit // unlock this aload\_2 // restore exception from local 2 athrow // rethrow return |
gets turned into the bytecode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | aload\_0 // load this invokestatic (SynchronizedMonitor.tryEnter) aload\_0 // load this dup astore\_1 // store in local 1 monitorenter // lock this aload\_0 // load this invokestatic (SynchronizedMonitor.enter) aload\_0 // load this invokevirtual (foo) // call foo() aload\_0 // load this invokestatic (SynchronizedMonitor.leave) aload\_1 // restore this from local 1 monitorexit // unlock this goto 26 // jump to return // handler for any exception (lines 9-10) astore\_2 // store exception in local 2 aload\_0 // load this invokestatic (SynchronizedMonitor.leave) aload\_1 // restore this from local 1 monitorexit // unlock this aload\_2 // restore exception from local 2 athrow // rethrow return |
which is really close to this bytecode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | aload\_0 // load this invokestatic (SynchronizedMonitor.tryEnter) aload\_0 // load this dup astore\_1 // store this in local 1 monitorenter // lock this aload\_0 // load this invokestatic (SynchronizedMonitor.enter) aload\_0 // load this invokevirtual (foo) // call foo() aload\_0 // load this invokestatic (SynchronizedMonitor.leave) goto 22 // jump beyond athrow // exception handler for all exceptions (lines 10-11, 17-18) astore\_2 // store exception in local 2 aload\_0 // load this invokestatic (SynchronizedMonitor.leave) aload\_2 // restore exception from local 2 athrow // rethrow aload\_1 // restore this from local 1 monitorexit // unlock this goto 33 // jump to return // exception handler for all exceptions (lines 8-24, 28-30) astore\_3 // store exception in local 3 aload\_1 // restore this from local 1 monitorexit // unlick this aload\_3 // restore exception from local 3 athrow // rethrow return |
which is what javac generates from this Java code:
1 2 3 4 5 6 7 8 9 10 | SynchronizedMonitor.tryEnter(this); synchronized(this) { SynchronizedMonitor.enter(this); try { foo(); } finally { SynchronizedMonitor.leave(this); } } |
Assuming the monitor methods whose calls have been inserted don’t throw exceptions, I think the instrumented version and the javac version are equivalent. But do I really have to make it this complicated? Is matching the javac output worth it?
I’ve also noticed that javac doesn’t seem to do any optimizations. I’m a bit shocked, but with a JIT compiler it probably makes sense. I actually hope this observation is correct and will remain valid. If javac does make optimizations, then this whole matching idea will immediately go out the door.
I’ll still have to make the synchronized methods to blocks instrumentor work again, and modify all the others… and of course I haven’t tested at all. More work to come, for sure. Good night.