[LLVMdev] Alternative exception handling proposal

Duncan Sands baldrick at free.fr
Fri Dec 3 09:08:36 PST 2010


Hi Bill, there is clearly a misunderstanding: either I am missing something
essential or you are.  To clear this up, I suggest you send me evil examples
and I will show you how my scheme handles them (or doesn't handle them, if I
am indeed failing to see something).

> This is the code that G++ generates from the example in my proposal:
...
> If the call to __Z3foov throws, we need to set up the tables to that it knows
> that it needs to call the __ZN1BD1Ev and __ZN1AD1Ev cleanups. This information
> requires looking at the invoke instruction – i.e., "where should I land?". It
> also needs to know which types it can catch in order to get the "action" variable.
>
> So the information is needed at the invoke site.
>
> The information is also needed at the site that makes the decision of which
> catch handler to execute (L19 in the above example). For one, it needs to know
> the action record table entries. And of course it needs to know the types that
> can catch, the personality function, and information about any filters. In your
> model, that point in the code is completely, and potentially irreversibly,
> separated from the invoke instruction.
>
> This is why I abandoned my original idea. There was no good way of modeling a
> relationship between the invoke and the catch handler decision site in the IR.

In my proposal I showed the IR generated by my scheme for the C++ code in your
example.  Since I don't understand what you are getting at here, perhaps you can
explain by referring to that IR, pointing out the problematic bits.

> You miss the point. As you well know, we need to know the specific type that is
> used for a catchall. E.g., i8* null in C++ and a global variable in Ada. In your
> code above, the i8* null is indistinguishable from the other types.

With my proposal we don't need to know that any more.

> Why is it in a "catches" clause?

Because it is an action like a catch.  This list corresponds exactly to what
gets put in the dwarf actions table.

>>>> I hate the way dwarf typeinfos, catches and filters are being baked into the
>>>> IR. Maybe metadata (see above) helps with this.
>>>
>>> Metadata cannot be counted on to remain.
>>
>> Is that also true for global metadata used as an argument to an intrinsic?
>
> You will need to read the documentation. But Chris never expects metadata to
> stick around. In fact, it's derived from Value, not Use. So how can you have a
> use of it in an intrinsic that the compiler would know about?

I don't know, but if you check the metadata testcases and docs you will see that
it can be used as an argument for an intrinsic through some magic.  Another
possibility is to replace all the catch info with a single global constant
(which would be in the llvm.metadata section) that contains the info.  So an
invoke would look like this (I left bitcasts off the global):

@catch32 = private constant { i8*, i1, { i8*, i8* }} = { @__gxx_personality_v0, 
false, { @_ZTIi, @_ZTIPKc } } ; would be in metadata section, I forget the syntax

...

     invoke void @_Z3foov()
           to label %invcont unwind label on @catch32

Or something like that.  Anyway, personalities, type infos etc would no
longer be hard-wired into the IR.

>
>> Do you have an idea for how to keep catches etc out of the definition of the
>> IR? I'm worried that if one day we add support for, say, SEH then we will
>> have to change how the IR is defined again, and that's better avoided.
>>
> I haven't given it a lot of thought. I don't like encoding DWARF-specific
> concepts into the IR. But my proposal doesn't do that either. It involves
> generic concepts that could be applied to all forms of EH.

I'm not sure they are generic.  Catches, cleanups, filters, typeinfos,
personality functions - how generic are these?   I don't know enough about
exception handling implementations on other platforms to say.

> John all ready mentioned problems with your inlining proposal.

He seems to have changed his mind once I added in a small but important detail
in my amendment (namely, that if an exception doesn't match the catches on an
invoke, and the cleanup flag is not set, then it is unspecified as to whether
it unwinds straight through the invoke, or branches to the landing pad).

Here is the code
> that brought up the need for what you instantly labeled a "horrible hack".

Sorry for the nasty name.  But you've got to admit that it's not beautiful, and
I have to admit that it solved a real problem.

It
> must work flawlessly with your new proposal.
>
> http://llvm.org/viewvc/llvm-project?view=rev&revision=99670
> <http://llvm.org/viewvc/llvm-project?view=rev&revision=99670>
>
> int main() {
> try {
>
>          throw new std::exception();
>      } catch (std::exception *e) {
>          throw e;
>      }
> }

Here's the IR with my scheme ("auto-generated" from the LLVM IR produced by
dragonegg at -O1 using the recipe described in my proposal).  A point of
interest is that the cleanup flag is set on the invoke in <L3>, so when it
throws excecution branches to <L1> (a cleanup) before unwinding continues
via the block labelled "rewind".  [Or the program just terminates at that
point if there is no handler further up the call stack, since there would
then only be cleanups all the way up the stack].

define i32 @main() noreturn {
entry:
   %D.2324_1 = invoke i8* @_Znwm(i64 8)
           to label %"<bb 3>" unwind label %"<L2>" personality 
@__gxx_personality_v0 catches %struct.__pointer_type_info_pseudo* @_ZTIPSt9exception

"<bb 3>":                                         ; preds = %entry
   %0 = bitcast i8* %D.2324_1 to i32 (...)***
   store i32 (...)** getelementptr inbounds ([5 x i32 (...)*]* 
@_ZTVSt9exception, i64 0, i64 2), i32 (...)*** %0, align 8
   %D.2306_4 = bitcast i8* %D.2324_1 to %struct.exception*
   %D.2305_5 = tail call i8* @__cxa_allocate_exception(i64 8) nounwind
   %D.2318_6 = bitcast i8* %D.2305_5 to %struct.exception**
   store %struct.exception* %D.2306_4, %struct.exception** %D.2318_6, align 8
   invoke void @__cxa_throw(i8* %D.2305_5, i8* bitcast 
(%struct.__pointer_type_info_pseudo* @_ZTIPSt9exception to i8*), void (i8*)* 
null) noreturn
           to label %1 unwind label %"<L2>" personality @__gxx_personality_v0 
catches %struct.__pointer_type_info_pseudo* @_ZTIPSt9exception

; <label>:1                                       ; preds = %"<bb 3>"
   unreachable

"<L1>":                                           ; preds = %"<L3>"
   %exc_ptr2 = tail call i8* @llvm.eh.exception()
   %filter3 = tail call i32 @llvm.eh.selector()
   tail call void @__cxa_end_catch() nounwind
   br label %rewind

"<L2>":                                           ; preds = %"<bb 3>", %entry
   %exc_ptr = tail call i8* @llvm.eh.exception()
   %filter = tail call i32 @llvm.eh.selector()
   %typeid = tail call i32 @llvm.eh.typeid.for(i8* bitcast 
(%struct.__pointer_type_info_pseudo* @_ZTIPSt9exception to i8*))
   %2 = icmp eq i32 %filter, %typeid
   br i1 %2, label %"<L3>", label %rewind

"<L3>":                                           ; preds = %"<L2>"
   %D.2320_8 = tail call i8* @__cxa_begin_catch(i8* %exc_ptr) nounwind
   %e.0_9 = bitcast i8* %D.2320_8 to %struct.exception*
   %D.2312_11 = tail call i8* @__cxa_allocate_exception(i64 8) nounwind
   %D.2321_12 = bitcast i8* %D.2312_11 to %struct.exception**
   store %struct.exception* %e.0_9, %struct.exception** %D.2321_12, align 8
   invoke void @__cxa_throw(i8* %D.2312_11, i8* bitcast 
(%struct.__pointer_type_info_pseudo* @_ZTIPSt9exception to i8*), void (i8*)* 
null) noreturn
           to label %3 unwind label %"<L1>" personality @__gxx_personality_v0 
cleanup

; <label>:3                                       ; preds = %"<L3>"
   unreachable

rewind:                                           ; preds = %"<L2>", %"<L1>"
   %rewind_tmp.0 = phi i8* [ %exc_ptr2, %"<L1>" ], [ %exc_ptr, %"<L2>" ]
   %rewind_sel.0 = phi i32 [ %filter3, %"<L1>" ], [ %filter, %"<L2>" ]
   call void @eh.set.exception(i8 *%rewind_tmp.0)
   call void @eh.set.selector(i32 %rewind_sel.0)
   unwind
}

It looks good to me, do you see anything wrong with it?

> And this code needs to give a sensible backtrace (on Darwin at least). It will crash libunwind because it was doing horrible things:
>
> #import <Foundation/Foundation.h>
>
> int main (int argc, const char * argv[]) {
> NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
>
> @try {
> @throw [NSException exceptionWithName:@"TestException" reason:@"Test" userInfo:nil];
> }
> @catch (NSException *e) {
> @throw e;
> }
>
>
> [pool drain];
> return 0;
> }

I was unable to compile this (I don't have Foundation).  However as my scheme
generates identical unwind tables to gcc (this is the whole point of it) I am
sure it will be fine!

Ciao, Duncan.



More information about the llvm-dev mailing list