[LLVMdev] RFC: Exception Handling Proposal Revised

Bill Wendling wendling at apple.com
Wed Dec 8 18:06:16 PST 2010


On Dec 7, 2010, at 12:30 PM, Duncan Sands wrote:

> Hi Bill, there are a couple of things I didn't understand about your proposal,
> for example how it interacts with inlining, whether it is feasible to do the
> "turn invoke-of-Unwind_Resume into a branch" optimization and also whether in
> "resumedest" you still plan to use _Unwind_Resume to continue unwinding up the
> stack.  Could you please show what the LLVM IR would look like for the following
> example both before and after inlining, and if the optimization I mentioned is
> compatible with the dispatch instruction can you please show how the IR would
> look after it is applied (right now it has the nasty habit of leaving calls to
> eh.selector far away from any landing pad, and I'm worried it might leave stray
> dispatch instructions in odd places, breaking the rules about where they may be
> placed).
> 
> Thanks a lot, Duncan.
> 
> #include <stdio.h>
> 
> struct X { ~X() { printf("Running destructor!\n"); } };
> 
> void foo() {
>   struct X x;
>   throw 1;
> }
> 
> int main() {
>   try {
>     foo();
>   } catch (int) {
>     printf("Caught exception!\n");
>   }
>   return 0;
> }

Hi Duncan,

I glossed over inlining a bit in my proposal, so let me step through your example to see how it would work. (I've elided some of the extraneous IR stuff – types, function decls, etc. – but none of the code.)

Here's the initial, non-inlined code:

@_ZTIi = external constant %struct.__fundamental_type_info_pseudo
@.str = private constant [20 x i8] c"Running destructor!\00", align 1
@.str1 = private constant [18 x i8] c"Caught exception!\00", align 1

define void @_Z3foov() noreturn optsize ssp {
entry:
  %tmp0 = tail call i8* @__cxa_allocate_exception(i64 4) nounwind
  %tmp1 = bitcast i8* %tmp0 to i32*
  store i32 1, i32* %tmp1, align 4
  invoke void @__cxa_throw(i8* %tmp0, i8* bitcast (%struct.__fundamental_type_info_pseudo* @_ZTIi to i8*), void (i8*)* null) noreturn
          to label %invcont unwind label %invcont1

invcont:
  unreachable

invcont1: landingpad
  %tmp2 = tail call i32 @puts(i8* getelementptr inbounds ([20 x i8]* @.str, i64 0, i64 0)) nounwind
  dispatch region label %invcont1 resume to label %Unwind
    personality [i32 (...)* @__gxx_personality_v0]

Unwind:
  %eh_ptr = tail call i8* @llvm.eh.exception()
  tail call void @_Unwind_Resume_or_Rethrow(i8* %eh_ptr)
  unreachable
}

define i32 @main() optsize ssp {
entry:
  invoke void @_Z3foov() optsize ssp
          to label %bb unwind label %lpad

bb:
  ret i32 0

lpad: landingpad
  %eh_ptr = tail call i8* @llvm.eh.exception()
  dispatch region label %lpad resume to label %Unwind
    catches [
      %struct.__fundamental_type_info_pseudo* @_ZTIi, label %ch.int
    ]
    personality [i32 (...)* @__gxx_personality_v0]

ch.int:
  %tmp0 = tail call i8* @__cxa_begin_catch(i8* %eh_ptr) nounwind
  %tmp1 = tail call i32 @puts(i8* getelementptr inbounds ([18 x i8]* @.str1, i64 0, i64 0))
  tail call void @__cxa_end_catch()
  ret i32 0

Unwind:
  tail call void @_Unwind_Resume(i8* %eh_ptr)
  unreachable
}

Here is a diagram of what the two functions look like:

foo:

    .--------------------.
    | invoke __cxa_throw |
    `--------------------'
             |
  .----------------------.
  | normal               | unwind
  v                      |
unreachable              v
          .-----------------------------.
          | print "Running destructor!" |
          |           dispatch          |----> resume
          `-----------------------------'


main:

    .------------.
    | invoke foo |
    `------------'
          |
  .-----------------.
  | normal          | unwind
  v                 |
ret 0               v
               .----------.
               | dispatch |----> resume
               `----------'
                    | catch ty: int
                    v
      .---------------------------.
      | print "Caught exception!" |
      |          ret 0            |
      `---------------------------'

>From this, you can imagine chaining the dispatches together. I.e., the inlined normal edge will point to the unreachable instruction. The 'ret 0' block in main will have no predecessors. The inlined dispatch's 'resume' edge will be modified to jump to main's landing pad. The '_Unwind_Resume' in the inlined function will have no predecessors.

Now in code (I won't replicate the "foo" function here since it doesn't change):

define i32 @main() optsize ssp {
entry.foo:
  %tmp0.foo = tail call i8* @__cxa_allocate_exception(i64 4) nounwind
  %tmp1.foo = bitcast i8* %tmp0.foo to i32*
  store i32 1, i32* %tmp1.foo, align 4
  invoke void @__cxa_throw(i8* %tmp0.foo,
                          i8* bitcast (%struct.__fundamental_type_info_pseudo* @_ZTIi to i8*),
                          void (i8*)* null) noreturn
          to label %invcont.foo unwind label %invcont1.foo

invcont.foo:
  unreachable

invcont1.foo: landingpad
  %tmp2.foo = tail call i32 @puts(i8* getelementptr inbounds ([20 x i8]* @.str, i64 0, i64 0)) nounwind
  dispatch region label %invcont1.foo resume to label %lpad.main
    personality [i32 (...)* @__gxx_personality_v0]

lpad.main: landingpad
  %eh_ptr = tail call i8* @llvm.eh.exception()
  dispatch region label %lpad.main resume to label %Unwind.main
    catches [
      %struct.__fundamental_type_info_pseudo* @_ZTIi, label %ch.int.main
    ]
    personality [i32 (...)* @__gxx_personality_v0]

ch.int.main:
  %tmp0.main = tail call i8* @__cxa_begin_catch(i8* %eh_ptr) nounwind
  %tmp1.main = tail call i32 @puts(i8* getelementptr inbounds ([18 x i8]* @.str1, i64 0, i64 0))
  tail call void @__cxa_end_catch()
  ret i32 0

Unwind.main:
  tail call void @_Unwind_Resume(i8* %eh_ptr)
  unreachable

;;; The blocks below have no predecessors

Unwind.foo: ;; << No more predecessors
  %eh_ptr = tail call i8* @llvm.eh.exception()
  tail call void @_Unwind_Resume_or_Rethrow(i8* %eh_ptr)
  unreachable

bb.main: ;; << No more predecessors
  ret i32 0
}

What the inliner would do in this case is look at the invoke/dispatch pair from foo. It would determine that it's a cleanup-only landing pad. And redirect the "resume to" edge to the resume destination for the inlinee's invoke/dispatch pair.

-bw





More information about the llvm-dev mailing list