I'm having trouble understanding the value in the way exceptions are
handled on Linux, the dwarf/system V ABI exception spec. The mechanism
allows for both cleanup routines and catch handlers, where by cleanup
handlers don't stop the search for a normal handler. The personality
function (I guess no longer part of the standard, but a C++ thing) can
also compare types of the landingpads.
I'm having trouble understanding a few points. I would like to
understand since I have exceptions in my language as well and want to
make effective use of the model.
Yes, it's not the most well-written spec I've ever had to read...
1. The way basic blocks come together often means you can have embedded
try/catch handlers without an intervening function call. You can't have
multiple landingpads without an intervening invoke thus one block needs
to branch/include the other handler. This appears to result in the need
to check the types of exceptions in the landingpad. This seems wasteful
to check the types in both the personality and the landingpad.
You can optimise this in the front end, by generating a single landing pad for invokes in the nested try blocks and having them emit a single landing pad that declares all of the catchable types in the order that they'll be tested and then jumps to the correct one. I'm not sure if clang does this for C++ or not.
LLVM could do this, but it requires some knowledge of the semantics of the personality function, so it's something that is only safe to turn on for known personality functions. Again, I'm not sure if LLVM does this (there are some optimisations that check the name of the personality function against ones that have known semantics).
2. Cleanup landingpads, in the general case, will need to call
functions. Not knowing what is in these functions one must assume they
could in turn have their own exception flow: they have a try/catch but
don't allow exceptions to escape. The ABI mechanism though seems like
only one exception at a time can be in progress. This would mean that
cleanup code would have to register as catch handlers and rethrow the
exception -- they cannot be "cleanup" routines according to the standard.
C++ supports nested exceptions, so when you throw an exception while catching another, the runtime library maintains a stack of them. The semantics of this are quite messy, and I don't remember them exactly, but they are (not very well) documented in Part III of the Itanium EH ABI doc. When something is thrown from a catch block in C++, the compiler emits an invoke for the throwing function that jumps to a block that calls the end-catch function and then resumes.
A simpler model is to have every call in a cleanup be an invoke with the unwind target a basic block that calls abort(). In practice, this is often enough...
In all I don't see why the current approach is better than simply
treating all landingpads equally in the personality. Each landingpad
would do its own type checking and rethrow or continue as appropriate.
That is, no cleanup routines, and no type matching in the personality.
There are three factors:
1) Having the personality function in a separate shared library means that you can modify its semantics (e.g. add support for dependent exceptions or add an optimised path if a new kind of type info is present) without having to recompile every shared library.
2) Exceptions are supposed to be infrequent and so shouldn't cause much code bloat. The unwind tables are in a separate section and so (hopefully) will only be paged in when an exception is thrown. Code at the landing pad is typically very close to the call and so will not just be paged in, it will probably also even be in the instruction cache. You want to minimise this, especially in a language like C++ that is designed for running microbenchmarks and already has significant problems with i-cache churn.
3) The code for selecting the correct exception can be very complex, especially in the presence of foreign exceptions. Inlining that is not likely to be a significant performance win, and will be optimising for the code path that, by definition, happens in exceptional circumstances.
 One interesting optimisation would be to move all basic blocks that are landing pads, or solely reachable from landing pads, away from the rest of the code when codegening the module, so that they will not even be swapped in when they are not used. The unwind ABI allows the unwind target to be a very large offset from the rest of the function, so it would be possible to stick all landing pads right at the end of the text section...