4

Writing my own compiler for a Java-like language, I am having trouble compiling synchronized blocks. I come up with the following idea to simplify them to try-finally blocks:

synchonized (obj) {
     statements...
}

Can be replaced with

Object _lock = obj
_monitorEnter(lock)
try {
    statements...
}
finally {
    _monitorExit(lock)
}

Where _monitorEnter and _monitorExit represent the MONITORENTER and MONITOREXIT instructions.

Am I correct with this assumption of how synchronized is compiled, or am I missing something?

EDIT

My implementation previously had some special handling for return and throw statements within the body. Basically, it would manually load all lock variables and MONITOREXIT them before each *RETURN or THROW instruction. Is this handled by the finally block, or do I still need these checks?

Clashsoft
  • 11,553
  • 5
  • 40
  • 79
  • 1
    Yes, this is correct. In fact, this is the _exact_ syntax for [`java.util.concurrent.locks.Lock`](https://docs.oracle.com/javase/tutorial/essential/concurrency/newlocks.html). – Boris the Spider Jan 23 '16 at 13:03
  • I’m not sure whether I understand your idea. If you are the one who implements the compiler, replacing `synchronized` by `try … finally` doesn’t simplify anything as you are still the one who has to implement `try … finally` then, aren’t you? So you still have to care for any `return` statement by yourself. – Holger Jan 25 '16 at 11:56
  • @Holger sure, but I would have to implement the same thing twice, so there is twice as many ways to make a mistake. Currently, the synchronized byte code generation is just a specialization of a try/finally statement. – Clashsoft Jan 25 '16 at 11:58
  • So it’s more a matter of treating them equally rather than substituting one for another. Actually, both, `synchronized` and `finally`, are constructs without an equivalent on the byte code level, but the same kind of workaround. Generally, when trying to develop a compiler for the JVM, it’s strongly recommended to know the entire [“Compiling for the Java Virtual Machine”](http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-3.html) chapter (if not the entire JVM spec)… – Holger Jan 25 '16 at 12:03

2 Answers2

5

Your assumptions are correct. The synchronized block in the Java language is implemented with monitorenter and monitorexit instructions. You can review the JVM specification details here.

Synchronization in the Java Virtual Machine is implemented by monitor entry and exit, either explicitly (by use of the monitorenter and monitorexit instructions) or implicitly (by the method invocation and return instructions).

The compiler generates bytecode that will handle all exceptions thrown inside a synchronized body, so your try-finally approach will work fine here.

Specification of finally statement does not tell anything about releasing monitors. Example provided in the first link shows bytecode for a simple method wrapped in synchronized block. As you can see, any possible exception is handled to ensure monitorexit instruction execution. You should implement same behaviour in your compiler (write code that will release monitor inside finally statement).

void onlyMe(Foo f) {
    synchronized(f) {
        doSomething();
    }
}

Method void onlyMe(Foo)
0   aload_1             // Push f
1   dup                 // Duplicate it on the stack
2   astore_2            // Store duplicate in local variable 2
3   monitorenter        // Enter the monitor associated with f
4   aload_0             // Holding the monitor, pass this and...
5   invokevirtual #5    // ...call Example.doSomething()V
8   aload_2             // Push local variable 2 (f)
9   monitorexit         // Exit the monitor associated with f
10  goto 18             // Complete the method normally
13  astore_3            // In case of any throw, end up here
14  aload_2             // Push local variable 2 (f)
15  monitorexit         // Be sure to exit the monitor!
16  aload_3             // Push thrown value...
17  athrow              // ...and rethrow value to the invoker
18  return              // Return in the normal case
Exception table:
From    To      Target      Type
4       10      13          any
13      16      13          any
D-side
  • 9,150
  • 3
  • 28
  • 44
AdamSkywalker
  • 11,408
  • 3
  • 38
  • 76
0

The Java compiler compiles synchronized blocks to something much like a try-finally, as you guessed. However, there is one minor difference - the exception handling catches exceptions thrown by monitorexit and loops infinitely trying to release the lock. There's no way to specify control flow quite like this in Java.

Antimony
  • 37,781
  • 10
  • 100
  • 107
  • Yeah, possibly looping infinitely on an exception is a great feature though I’m not sure whether this really is mandatory, just because it appears in one example code of the specification… – Holger Jan 25 '16 at 11:48