Exception Handling

Before this week, I had no idea how exception handling worked. I assumed languages which use virtual machines do a lot of bookkeeping in order to maintain some data structure to jump back through, but C++ exceptions may as well have been magic.

During the last week, I’ve come to understand a lot more about how they work, and also struggled a lot at working out how to implement them in Plinth.

There are two main ways to do exception handling: use the Itanium ABI, or use calls to setjmp() and longjmp().

Itanium ABI for Exception Handling

The Itanium ABI, and the variations of it used on other architectures, can also be referred to as “zero-cost” exception handling. This is because they don’t use any CPU time or stack space unless an exception is actually thrown.

The core of this form of exception handling is implemented by a library called an unwinder. The unwinder is what you call to raise an exception in the first place, and it knows how to unwind the stack and call something called a “personality function” for each function call it unwinds.

A personality function is a language-specific function which works out what to do when the unwinder unwinds into a given stack frame; it is passed the exception being thrown and the unwinder’s context for this frame. From this context, it can find out what the instruction pointer was at the time of the call, and it uses this in combination with some language-specific data about the current function to decide whether to continue unwinding past this frame, or to jump into a catch or finally block.

It then sets some registers, including the instruction pointer, and execution jumps to a landing pad. This landing pad does whatever cleanup the personality function signalled it to do. So if the personality function decided that one of the catch blocks was triggered, one of the registers it set would indicate that to the landing pad, which would branch on the contents of that register and execute that catch block.

If a finally block was encountered, and it didn’t throw another exception or return in the mean-time, it would eventually call back into the unwinder to resume the unwinding process from where it left off.

setjmp() / longjmp() Exception Handling

setjmp() and longjmp() are C functions which have the interesting semantics that when you call longjmp(jmp_buf, value), execution jumps directly to the original call to setjmp(jmp_buf), which then returns for a second time, this time returning the value passed into longjmp().

The most important part of this form of exception handling is its linked-list structure. Each function which needs to handle exceptions allocates an entry in this linked-list on the stack, inside which it stores a reference to the previous piece of memory along with a buffer for the setjmp() and longjmp() functions to use. The current top-of-the-stack is stored in a thread-local variable.

In order to call a function, the execution state must first have been saved to the function’s buffer using setjmp(), so that if the function throws an exception, execution can begin again at the relevant catch or finally block.

Apart from the linked-list allocated on the stack, this method of exception handling is very similar to the Itanium ABI. In particular, each linked-list entry contains a pointer to a personality function, which does the same sorts of processing (using the same sort of language-specific data) that the Itanium ABI’s personality function does. The main difference is that it unwinds using longjmp() rather than setting the program counter directly.

As you might have noticed, setjmp() / longjmp() exception handling is much more expensive than it is with the Itanium ABI, as it uses more CPU time and stack space maintaining the linked-list structure. It can be faster when propagating exceptions, but since exception handling is meant to be rare, the Itanium ABI is the preferred method for exception handling most of the time.

LLVM’s Exception Handling

LLVM supports both the zero-cost method and setjmp() / longjmp(). The problem is that it doesn’t support either of them on all platforms.

One example for this is ARM. On ARM, the zero-cost exception handling ABI (EHABI) is slightly different from the Itanium ABI. The unwinder still calls the personality function once per stack frame, but it also delegates the unwinding of that stack frame to the personality function (i.e. the personality function must know how to unwind out of the current function).

From what I can tell, LLVM doesn’t yet emit the correct DWARF exception tables (or language specific data blocks, as they are called in the Itanium ABI) necessary to support ARM’s EHABI (http://llvm.org/bugs/show_bug.cgi?id=7187), which is why LLVM recommends using setjmp()/longjmp() on ARM.

Another problem is that setjmp() / longjmp() exception handling is broken on most platforms which are not ARM: http://llvm.org/bugs/show_bug.cgi?id=1642

Plinth’s Exception Handling

With Plinth, I am trying as hard as I can to keep the generated bitcode files platform independent. This means generating platform-independent code to handle exceptions in a given function. The problem is that from what I can tell, neither of LLVM’s methods for exception handling work on all platforms.

In order to get around this, I will probably have to implement my own exception handling system. The design I am currently working on is very similar to the normal setjmp() / longjmp() system, but without personality functions and without any form of DWARF exception tables (since I cannot generate DWARF myself).

Hopefully, next week I will have a basic exception handling system implemented.

Update 2013/02/12: After asking the LLVM developers about exactly how setjmp() / longjmp() exceptions are written in bitcode, it turns out that the bitcode is the same no matter which scheme you use (email link). Since this allows me to generate bitcode that will work on all platforms (given a compatible unwinding library and a personality function), I will be using zero-cost exceptions where possible, and falling back to SJLJ exceptions everywhere else.

One thought on “Exception Handling

  1. Pingback: More Exceptions: Semantics « Anthony's Blog

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.