Exceptions for my own programming language (original) (raw)
I am working on an back end for my own programming language. I think the most challenging feature will be exceptions. I’ve read the documentation, but I’m still missing some crucial pieces.
What I (think I) know:
- Exceptions are thrown using the
_Unwind_RaiseException
function from libunwind. - Functions that can throw an exception need a personality function (but I don’t know what exactly personality functions do)
- All functions in a
try { ... }
block need to be called withinvoke
which includes references to a label for normal return and another label for exceptions. - The exceptional label should point to a basic block that starts with a
landingpad
instruction.
My main question now is about the landingpad
arguments. In particular, I need to provide at least one catch
clause with a “type” of exception. But which type should I use there? I don’t have to provide a type when using _Unwind_RaiseException
, so how does the mechanism know that my exception should land on this landingpad?
I would also appreciate any information about what the personality function is supposed to do, because the LLVM documentation is really light on the details:
The
personality
attribute permits functions to specify what function to use for exception handling.
In particular, about the personality function, I’m wondering how it should jump to the LLVM landing pad.
Unfortunately, exception handling in LLVM is a bit of a mess with
several “secret” handshakes in play that’s not well-documented in
general. I myself am not the most well-versed in this topic, but let me
provide what information again.
The basic structure of exception handling is described in the Itanium
C++ ABI [1] here: C++ ABI for Itanium: Exception Handling.
There are several slightly different variations (e.g., ARM C++ exception
handling is different), but the core idea is essentially this:
- There’s a core, effectively OS-provided, library for unwinding the
stack with an “exception object” (this is the_Unwind_RaiseException
function). This function knows how to do the necessary stack walking for
the target in question. - That library relies on the existence of a function, provided by the
language support library, to work out what needs to be done based on the
kind of exception being thrown. This function is the “personality function.” - The personality function will essentially look in a table to map the
return address and the “type” of exception to the action that needs to
be done (return to this stack frame, potentially after tweaking
registers somewhat, or continue unwinding).
As you can see, there’s essentially some amount of secret handshake
going on, with LLVM needing to generate a special exception-handling
table, the format of which is largely dictated by the personality
function, although most of them are in practice basically the same as
the one used by __gxx_personality_v0
(I don’t know the degree to which
LLVM supports unknown personality functions).
The way LLVM handles Itanium-style exception handling is to use thelandingpad
to define essentially what the entry in the special lookup
table should look like, and the result of the instruction itself is the
registers set up by the personality function before returning control to
(the exceptional return address of) the function. It’s probably best to
look at how Clang actually generates the LLVM code to get an
understanding of what code looks like: Compiler Explorer
(I’ve disabled demangling so that you don’t get confused as to what the
output looks like, given that the _ZTI* names all demangle as “typeinfo
for xyz”).
For the standard __gxx_personality_v0
, the first return value is the_Unwind_Exception *
object passed into _Unwind_RaiseException
(which
is not the same as the address of the object being thrown!), and the
second return value is the type index of the handler.
As for the values you should fill in into the catch lines of the
landingpad… that’s part of the secret handshake between you and your
personality function!
[1] Despite its name, the Itanium ABI is the C++ ABI that is common
essentially for every platform except compiling with MSVC.