Skip navigation

Over the last two days I implemented exception handling support in Fera. This was quite challenging, as the model used for try/catch/fault/finally blocks in CIL is rather cumbersome, and requires a lot of love and attention to get working properly for all cases. I figured I’d write about some of the things I’d learned.

First, CIL represents “protected” blocks via a special metadata table associated with each method body. Each protected block directive is an entry in this table. It’s first worth noting that each entry can only specify a single handler, and that handler must be one of catch, finally, filter, or fault. C# only exposes catch and finally, though it’s possible they may make use of the other two in some manner. Each try block is defined by a range of instructions, specified by their offsets. The ‘start’ part of the try block is the first protected instruction in the block, while the ‘end’ part of the try block is the instruction immediately proceeding the last instruction in the block. This is important to note, as setting the end instruction of a try block to the tailing ‘leave’ instruction which routes control flow to where it should go will result in verification errors from peverify/ilverify, and may even cause the runtime to emit an InvalidProgramException.

In addition, it is illegal to have more than one type of handler per try block, meaning something like:

try {
   // x x x 
} catch (Exception) {
   // x x x
} finally {
   // x x x 
}

must be split into a pair of try blocks like this:

try {
   try {
      // x x x 
   } catch (Exception) {
      // x x x
   }
} finally {
   // x x x 
}

Not doing this results in an InvalidProgramException when the compiled program is run.

Another quirk is the behavior of the ‘ret’ instruction within a protected block. If used, the finally handlers will not run (and more than likely you’ll get runtime complaints, in addition to verification errors). When returning inside a protected block, the ‘leave’ instruction should be used, with a target instruction which is at the tail of the method. When ‘leave’ executes, control is transferred to the finally block first.

If the method you’re compiling has a return value, the value to return must be saved in an intermediate variable, because transfer to the finally block will clear/change or otherwise mess up the stack. So to put these ‘return’ requirements together, it is necessary for the code controlling the method compilation to be aware of whether the method contains any try blocks which contain ‘return’ statements, unless you don’t mind generating some useless instructions that are unreachable when there aren’t any ‘return’ statements that use it.

On another note, I implemented a new part of the code flow analysis which checks for paths that don’t return when the method return type is not void. This doesn’t require dominators or anything crazy, just a basic code flow graph. I just winged it really but the algorithm’s time efficiency is O(nb) where n = the maximum depth of the code flow graph (ignoring recursions) and b = the average number of branches in a boundary block. The algorithm employs batch processing instead of recursion, so the memory requirements are very slightly higher, namely O(2n + b) worst case. It might be more efficient to use recursion, but managing to avoid following loops would be more difficult, and it would be harder to track for debugging. I’m happy with the one I’ve got, it required very little work to get working properly (causing no regressions even!)

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: