The Fan try/catch/finally design uses the exact same semantics as Java and C#. Like C# (but unlike Java) it is illegal to use a return, break, or continue statement inside a finally block (spaghetti code) and you will receive a compiler error. The devil is in the details required for fcode to support both the Java and CLR environments.
Java try blocks specify a range of instructions which map to catch handlers. Finally blocks are implemented at a low level using the jsr/ret instructions to create an "in-method subroutine". It is the responsibility of the compiler writer to ensure that all exits from a try block correctly jsr to the finally handler including a return, break, or continue statement. To ensure that catch blocks call the finally handler they too are wrapped in a higher level catch such that if an exception is thrown in an a catch block the finally handler is still called.
The CLR has a higher level exception handling mechanism - finally blocks are automatically invoked - there is no need for the compiler write to explicitly generate jsr/ret instructions. But the CLR requires that the compiler writer use the special "leave" instruction rather than "br" when jumping out of a "protected region" so that the finally handlers can be correctly invoked. You may never return from a protected region - rather you must stash the return result in a local variable, leave the protected region, load your result from the local variable, then do the actual return instruction.
The Fan design for generating try/catch/finally fcode is the union of these approaches to make Java and CLR emitting fast and easy. Consider the following simple try/catch:
Note that we stash the result in both the try and catch blocks to a local variable called "$return", then use a Leave instruction to jump to the end of the method where we generate our Return instruction. Also note that we bracket the catch block using the CatchAllStart and CatchEnd instructions. For Java we use the CatchAllStart/CatchErrStart to save or pop the exception and CatchEnd is a nop. The Leave instruction is treated just like a Jump instruction.
Just like the try/catch we use a Leave instruction to jump out of the protected region. Also notice that the compiler inserts the JumpFinally instruction before leaving the protected region. The finally handler itself is bracketed by the FinallyStart and FinallyEnd instructions. We generate an entry in the ErrTable to route any exception to the FinallyStart block - all error handlers will start with CatchAllStart, CatchErrStart, or FinallyStart as an quick and easy way to see what kind of block it is.
In Java the JumpFinally emits the jsr instruction. The FinallyStart emits both the "catch all" handler as well as the code to stash the stack pointer to a local variable. Thus the ErrTable maps straight to the catch all required for Java to trap exceptions, call finally, then rethrow the exception. So FinallyStart actually maps to 5 Java instructions and the JumpFinally is emited as a jsr that actually jumps over the catch all block to the instruction which stashes the stack pointer.
It's freaking complicated that's for sure!
andyTue 19 Dec 2006
The CLR implementation differs in three ways. Leave instructions are emitted with the IL "leave" opcode (not jumps like Java), which are required to leave protected regions. The JumpFinally is ignored in the IL emitter because the CLR handles invoking the finally block automatically for us. And the FinallyEnd instruction is emitted as the "endfinally" IL opcode, which is required to leave a finally handler.
andyWed 18 Jun 2008
We want to map .NET's native exceptions to Fan types where possible (i.e. NullReferenceException -> NullErr). So in the case of try-catch blocks, we really want to catch both the native and Fan type in C#:
Obviously C# doesn't support this, so I thought I would get tricky and try to map the same handler block for both exception types in the emitted IL:
// IL
...
.try 0 to 8 catch NullReferenceException handler 8 to 39
.try 0 to 8 catch NullErr handler 8 to 39
Unfortunately, the CLR does not allow this. But luckily, the CLR does provide filters to allow us to get around this. So when I detect that an exception is being caught that can map to a native type, I'll emit a filter block instead:
// IL
...
.try 0 to 8 filter 8 handler 15 to 39
Where the filter simply uses isinst to check if the exception is either NullErr or NullReferenceException, and if true, pushes 1 onto the stack, which invokes the filter handler.
brian Tue 5 Dec 2006
The Fan try/catch/finally design uses the exact same semantics as Java and C#. Like C# (but unlike Java) it is illegal to use a return, break, or continue statement inside a finally block (spaghetti code) and you will receive a compiler error. The devil is in the details required for fcode to support both the Java and CLR environments.
Java try blocks specify a range of instructions which map to catch handlers. Finally blocks are implemented at a low level using the jsr/ret instructions to create an "in-method subroutine". It is the responsibility of the compiler writer to ensure that all exits from a try block correctly jsr to the finally handler including a return, break, or continue statement. To ensure that catch blocks call the finally handler they too are wrapped in a higher level catch such that if an exception is thrown in an a catch block the finally handler is still called.
The CLR has a higher level exception handling mechanism - finally blocks are automatically invoked - there is no need for the compiler write to explicitly generate jsr/ret instructions. But the CLR requires that the compiler writer use the special "leave" instruction rather than "br" when jumping out of a "protected region" so that the finally handlers can be correctly invoked. You may never return from a protected region - rather you must stash the return result in a local variable, leave the protected region, load your result from the local variable, then do the actual return instruction.
The Fan design for generating try/catch/finally fcode is the union of these approaches to make Java and CLR emitting fast and easy. Consider the following simple try/catch:
Generates the following fcode:
Note that we stash the result in both the try and catch blocks to a local variable called "$return", then use a Leave instruction to jump to the end of the method where we generate our Return instruction. Also note that we bracket the catch block using the CatchAllStart and CatchEnd instructions. For Java we use the CatchAllStart/CatchErrStart to save or pop the exception and CatchEnd is a nop. The Leave instruction is treated just like a Jump instruction.
Now let's look at a simple try/finally block:
Generates the following fcode:
Just like the try/catch we use a Leave instruction to jump out of the protected region. Also notice that the compiler inserts the JumpFinally instruction before leaving the protected region. The finally handler itself is bracketed by the FinallyStart and FinallyEnd instructions. We generate an entry in the ErrTable to route any exception to the FinallyStart block - all error handlers will start with CatchAllStart, CatchErrStart, or FinallyStart as an quick and easy way to see what kind of block it is.
In Java the JumpFinally emits the jsr instruction. The FinallyStart emits both the "catch all" handler as well as the code to stash the stack pointer to a local variable. Thus the ErrTable maps straight to the catch all required for Java to trap exceptions, call finally, then rethrow the exception. So FinallyStart actually maps to 5 Java instructions and the JumpFinally is emited as a jsr that actually jumps over the catch all block to the instruction which stashes the stack pointer.
It's freaking complicated that's for sure!
andy Tue 19 Dec 2006
The CLR implementation differs in three ways. Leave instructions are emitted with the IL "leave" opcode (not jumps like Java), which are required to leave protected regions. The JumpFinally is ignored in the IL emitter because the CLR handles invoking the finally block automatically for us. And the FinallyEnd instruction is emitted as the "endfinally" IL opcode, which is required to leave a finally handler.
andy Wed 18 Jun 2008
We want to map .NET's native exceptions to Fan types where possible (i.e. NullReferenceException -> NullErr). So in the case of try-catch blocks, we really want to catch both the native and Fan type in C#:
Obviously C# doesn't support this, so I thought I would get tricky and try to map the same handler block for both exception types in the emitted IL:
Unfortunately, the CLR does not allow this. But luckily, the CLR does provide filters to allow us to get around this. So when I detect that an exception is being caught that can map to a native type, I'll emit a filter block instead:
Where the filter simply uses
isinst
to check if the exception is either NullErr or NullReferenceException, and if true, pushes 1 onto the stack, which invokes the filter handler.