[LLVMdev] Alternative exception handling proposal

Duncan Sands baldrick at free.fr
Thu Dec 2 04:26:27 PST 2010


Hi John,

>> Inlining
>> --------
>>
>> Many a plausible seeming exception handling scheme has fallen by the way-side
>> because it interacts poorly with inlining.
>>
>> Here is how inlining would work with this scheme.  It's pretty close to how
>> it works right now.  Suppose you have
>>
>>    invoke void @foo()
>>            to label %invcont unwind label %lpad<foo catch info>
>>
>> and you want to inline foo.  Suppose foo contains an invoke:
>>
>>    invoke void @bar()
>>            to label %invcont2 unwind label %lpad2<bar catch info>
>>
>> Then after inlining you have an invoke of bar in which foo's catch info
>> has been appended to bar's:
>>
>>    invoke void @bar()
>>            to label %invcont2 unwind label %lpad2<joined catch info>
>>
>> What does appending<foo catch info>  to<bar catch info>  mean?  If the
>> personality functions are different then you are in trouble and need to
>> disallow the inlining!  The cleanup flag is the "or" of the foo and bar
>> cleanup flags.  The catches are the bar catches followed by the foo
>> catches.
>
> You are greatly underestimating the amount of work the inliner has to do under this proposal.  In fact, the only thing that your proposal simplifies is DwarfEHPrepare.

needless to say, I disagree :)  See below.

> The inliner would still need to do two extra things:
>
> 1.  It would need to adjust landing pads so that they forward selectors they don't understand to the enclosing landing pad.  Consider the following code:
>
>   void a();
>   void b() {
>     try { a(); } catch (int i) {}
>   }
>   void c() {
>     try { b(); } catch (float f) {}
>   }
>
> The landing pad in b() only has one case to worry about, so it's naturally going to immediately enter the catch handler for 'int'.  This is obviously semantically wrong if the combined invoke unwinds there.

Here is the LLVM IR for functions "b" and "c".

define void @_Z1bv() {
entry:
   invoke void @_Z1av()
           to label %return unwind label %"<L1>" personality 
@__gxx_personality_v0 catches %struct.__fundamental_type_info_pseudo* @_ZTIi

"<L1>":                                           ; preds = %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.__fundamental_type_info_pseudo* @_ZTIi to i8*))
   %0 = icmp eq i32 %filter, %typeid
   br i1 %0, label %"<L2>", label %"<bb 5>"

"<bb 5>":                                         ; preds = %"<L1>"
   unwind

"<L2>":                                           ; preds = %"<L1>"
   %D.2112_2 = tail call i8* @__cxa_begin_catch(i8* %exc_ptr) nounwind
   tail call void @__cxa_end_catch() nounwind
   ret void

return:                                           ; preds = %entry
   ret void
}

define void @_Z1cv() {
entry:
   invoke void @_Z1bv()
           to label %return unwind label %"<L1>" personality 
@__gxx_personality_v0 catches %struct.__fundamental_type_info_pseudo* @_ZTIf

"<L1>":                                           ; preds = %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.__fundamental_type_info_pseudo* @_ZTIf to i8*))
   %0 = icmp eq i32 %filter, %typeid
   br i1 %0, label %"<L2>", label %"<bb 5>"

"<bb 5>":                                         ; preds = %"<L1>"
   unwind

"<L2>":                                           ; preds = %"<L1>"
   %D.2106_2 = tail call i8* @__cxa_begin_catch(i8* %exc_ptr) nounwind
   tail call void @__cxa_end_catch() nounwind
   ret void

return:                                           ; preds = %entry
   ret void
}


Here is the LLVM IR when you inline "b" into "c" according to the rules I
stated:

define void @_Z1cv() {
entry:
   invoke void @_Z1av()
           to label %return.i unwind label %"<L1>.i" personality 
@__gxx_personality_v0 catches %struct.__fundamental_type_info_pseudo* @_ZTIi, 
%struct.__fundamental_type_info_pseudo* @_ZTIf

"<L1>.i":                                         ; preds = %entry
   %exc_ptr.i = call i8* @llvm.eh.exception()
   %filter.i = call i32 @llvm.eh.selector()
   %typeid.i = call i32 @llvm.eh.typeid.for(i8* bitcast 
(%struct.__fundamental_type_info_pseudo* @_ZTIi to i8*))
   %0 = icmp eq i32 %filter.i, %typeid.i
   br i1 %0, label %"<L2>.i", label %"<bb 5>.i"

"<bb 5>.i":                                       ; preds = %"<L1>.i"
   br label %"<L1>"

"<L2>.i":                                         ; preds = %"<L1>.i"
   %D.2112_2.i = call i8* @__cxa_begin_catch(i8* %exc_ptr.i) nounwind
   call void @__cxa_end_catch() nounwind
   br label %_Z1bv.exit

return.i:                                         ; preds = %entry
   br label %_Z1bv.exit

_Z1bv.exit:                                       ; preds = %return.i, %"<L2>.i"
   br label %return

"<L1>":                                           ; preds = %"<bb 5>.i"
   %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.__fundamental_type_info_pseudo* @_ZTIf to i8*))
   %1 = icmp eq i32 %filter, %typeid
   br i1 %1, label %"<L2>", label %"<bb 5>"

"<bb 5>":                                         ; preds = %"<L1>"
   unwind

"<L2>":                                           ; preds = %"<L1>"
   %D.2106_2 = tail call i8* @__cxa_begin_catch(i8* %exc_ptr) nounwind
   tail call void @__cxa_end_catch() nounwind
   ret void

return:                                           ; preds = %_Z1bv.exit
   ret void
}


Looks good, right?  It is true that I forgot to say that front-ends have to
output code to continue unwinding an exception if they don't recognise the
selector.  I amended my proposal to say that it is unspecified whether a
non-matching exception results in a branch to the unwind label or not - this
forces front-ends to rewind any exceptions that don't match any of their
catches.

>
> 2.  It would need to rewrite calls to _Unwind_Resume on cleanup-only paths if the enclosing invoke has a handler.  The EH machinery does not expect a landing pad which claims to handle an exception to just call _Unwind_Resume before handling it;  that's why we currently have to use hacks to call _Unwind_Resume_or_Rethrow instead.

Nope, because when inlining through an invoke the inliner appends the invoke's
catch info to everything it inlines.  This is the key point that makes the
cleanup problem go away.  As an optimization it is possible to convert an invoke
of _Unwind_Resume into a branch, but I think it would be better for front-ends
to output an "unwind" instruction instead of _Unwind_Resume and do this
optimization on "unwind".

> Also, some platforms provide an alternative to _Unwind_Resume that doesn't require the compiler to pass the exception pointer;  we'd like to be able to use those.

The code generators can lower the "unwind" instruction to whatever the platform
prefers to use to continue unwinding the exception.  For example on ARM with
EABI you wouldn't want unwind to be lowered to _Unwind_Resume.

>
> The 'dispatch' instruction simplifies this by presenting an obvious place to rewrite (which doesn't actually require rewriting — you just add/redirect the 'resume' edge to point to the enclosing landing pad).

As explained above no rewriting is needed with this scheme.

Best wishes,

Duncan.



More information about the llvm-dev mailing list