[LLVMdev] RFC: Exception Handling Rewrite

Bill Wendling wendling at apple.com
Fri Jul 22 22:29:54 PDT 2011


What? Yet another EH proposal?! This one is different from the others in that
I'm planning to start implementing this shortly. But I want your feedback! I've
all ready gotten a lot of feedback from Chris, John, Jim, Eric, and many others.
Now is your turn!

Please read this proposal and send me your comments, suggestions, and concerns.

-bw

//===----------------------------------------------------------------------===//
//                     LLVM Exception Handling Rewrite
//===----------------------------------------------------------------------===//

7/16/2011 - Initial revision
7/18/2011 - Chris's feedback incorporated
7/19/2011 - John's feedback incorporated
7/22/2011 - Final revision: Chris's feedback incorporated

The current exception handling system works for the most part. We have been able
to work with our existing implementation now and get it to produce exception
handling code for a wide variety of situations. However, we are reaching the
limit of what we are able to support with it. In particular, it suffers from
these deficiencies:

1. It's very hard to perform inlining through an `invoke' instruction. Because
   the information is stored in an intrinsic, it's very difficult to merge one
   function's exception handling with another.
2. The EH intrinsics, which contain the exception handling information for an
   invoke (e.g., 'llvm.eh.exception' and 'llvm.eh.selector'), can move out of
   the landing pad during normal code motion thus making retrieving EH
   information very difficult.
3. We are currently able to inline two functions which have incompatible
   personality functions. This breaks the semantics of the original program.
4. The exception handling ABI is not followed. Instead, we approximate it using
   catchalls and calls to non-standard APIs (e.g., '_Unwind_Resume_or_Rethrow').
5. It's inefficient. Because of the constant rethrowing of exceptions, a normal
   exception takes much longer to execute.

In order to address these issues, we need a much better way to represent
exceptions in LLVM IR.

//===----------------------------------------------------------------------===//
// Proposal
//===----------------------------------------------------------------------===//

We start with the existing system and try to modify it to eliminate its negative
aspects. This has many benefits, not the least of which is that it's easier for
existing front-ends to adopt the new exception handling design.

The heart of the proposal is to directly associate unwinding information for an
invoke with the invoke, and to directly expose the values produced in the
landing pad.  We do this by introducing a new 'landingpad' instruction which is
always required to the first non-phi instruction in the 'unwind' block of a
landing pad block.  Because of this direct association, it is always possible to
find the invoke for a landing pad, and always possible to find the landing pad
for an invoke.

The 'landingpad' instruction is an instruction (not an intrinsic) because it
has a variadic but highly structured argument list, and can return arbitrary
types (specified by the personality function and ABI).

//===--------------------------
// The 'landingpad' Instruction
//

The 'landingpad' instruction replaces the current 'llvm.eh.exception' and
'llvm.eh.selector' intrinsics.

// Syntax:

  %res = landingpad <somety> personality <ty> <pers_fn> <clause>+

where

  <clause> :=
       cleanup
    |  catch <ty_1>, <ty_2>, ..., <ty_n>
    |  filter <ty_1>, <ty_2>, ..., <ty_m>

and the result has the type '<somety>'. The personality functions must be the
same for all landingpad instructions in a given function.

A landingpad instruction must contain at least one cleanup, catch, or filter
clause.

// Restrictions:

There are several new invariants which will be enforced by the verifier:

1. A landing pad block is a basic block which is the unwind destination of an
   invoke instruction.
2. A landing pad block must have a landingpad instruction as its first non-PHI
   instruction.
3. The landingpad instruction must be the first non-PHI instruction in the
   landing pad block.
4. Like indirect branches, splitting the critical edge to a landing pad block
   requires considerable care, and SplitCriticalEdge will refuse to do it.
5. All landingpad instructions in a function must have the same personality
   function.

// Semantics:

The landingpad instruction defines the values which are set by the personality
function upon reentry to the function, and therefore the "result type" of the
landing pad instruction.  With these changes, LLVM IR will be able to represent
unusual personality functions that could return things in 6 registers for
example.  As with calling conventions, how the personality function results are
represented in LLVM IR is target specific.

// Examples:

  ;; A landing pad which can catch an integer or double and which can throw only
  ;; a const char *.
  %res = landingpad { i8*, i32 } personality i32 (...)* @__gxx_personality_v0
           catch i8** @_ZTIi, i8** @_ZTId
           filter i8** @_ZTIPKc

  ;; A landing pad that is a cleanup.
  %res = landingpad { i8*, i32 } personality i32 (...)* @__gxx_personality_v0
           cleanup

  ;; A landing pad which indicates that the personality function should call the
  ;; terminate function.
  %res = landingpad { i8*, i32 } personality i32 (...)* @__gxx_personality_v0
           terminate

//===--------------------------
// The 'resume' Instruction
//

The new "resume" instruction replaces the 'llvm.eh.resume' intrinsic and the old
"unwind" instruction.  The "unwind" instruction will be removed.

// Syntax:

  resume <somety> <op>

This is a terminator instruction that has no successors. Its operand must have
the same type as the result of any landingpad instructions in the same function.

// Semantics:

Resumes propagation of an existing (in-flight) exception.

// Example:

  resume { i8*, i32 } %eh.val   ;; Resume exeception propagation out of
                                ;; current function.

Note that there is no way with this proposal for pure IR to actually start an
exception throw.  Instead, use of __cxa_throw or something similar is required.

//===----------------------------------------------------------------------===//
// Inlining
//===----------------------------------------------------------------------===//

During inlining through an invoke instruction, a 'resume' instruction will be
replaced by a branch to the instruction immediately after the landingpad
instruction associated with the invoke. The landingpad instructions, which
supply the argument to the 'resume', will be updated with new types which they
can catch, if any, from the invoke's landing pad block.

The inliner will refuse to inline two functions which have landingpad
instructions with incompatible personality functions. For example, an Ada
function cannot be inlined into a C++ function, because Ada uses an incompatible
personality function. However, a C++ function may be inlined into an Objective-C
function (and vice-versa), because the Objective-C personality function contains
all of the functionality of the C++ personality function.

This proposal does not include a way for the optimizer to know that a superset
relation exists, that can be added in the future with a named MDNode.  For now,
all such inlinings will be refused.

//===----------------------------------------------------------------------===//
// Cleanup and Catch Clauses
//===----------------------------------------------------------------------===//

If there is a cleanup that's inlined into a try-catch block, the exception
handling table will still mark it as a cleanup, but will also indicate that it
catches specific types. For example:

struct A {
  A();
  ~A();
};

void qux();
void bar() {
  A a;
  qux();
}

void foo() {
  try {
    bar();
  } catch (int) {
  }
}

If the call to bar is inlined into foo, the landing pads for A's constructor and
destructor are marked as catching an int. The landing pad for qux(), which is
marked as a cleanup in bar(), will remain marked as a cleanup, but also be
marked as catching an int.

//===----------------------------------------------------------------------===//
// Future Landing Pad Instruction Optimizations
//===----------------------------------------------------------------------===//

In the future, the landing pad instruction could be modified to aide
optimizations. E.g., if the personality function can support it, a landingpad
instruction may indicate that the personality function should call the
'terminate' function:

  %res = landingpad <somety> personality <ty> <pers_fn>
           terminate

This landingpad instruction would be followed by an 'unreachable'
instruction. The exception handling table would be set up to have the
personality function call the appropriate 'terminate' function.

Support for such features would be done on a case-by-case basis.

//===----------------------------------------------------------------------===//
// Examples
//===----------------------------------------------------------------------===//

1) A simple example:

#include <cstdio>

void bar();

void foo() throw (const char *) {
  try {
    bar();
  } catch (int i) {
    printf("caught integer %d\n", i);
  } catch (double d) {
    printf("caught double %g\n", d);
  }
}

Produces:

@_ZTIPKc = external constant i8*
@_ZTIi = external constant i8*
@_ZTId = external constant i8*
@.str = private unnamed_addr constant [19 x i8] c"caught integer %d\0A\00", align 1
@.str1 = private unnamed_addr constant [18 x i8] c"caught double %g\0A\00", align 1

define i32 @_Z3foov() uwtable optsize ssp {
entry:
  invoke void @_Z3barv() optsize
          to label %try.cont unwind label %lpad

invoke.cont7:
  %tmp0 = tail call i8* @__cxa_begin_catch(i8* %exn) nounwind
  %tmp1 = bitcast i8* %tmp0 to i32*
  %exn.scalar = load i32* %tmp1, align 4
  %call = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([19 x i8]* @.str, i64 0, i64 0),
                                            i32 %exn.scalar) optsize
  tail call void @__cxa_end_catch() nounwind
  br label %try.cont

invoke.cont20:
  %tmp2 = tail call i8* @__cxa_begin_catch(i8* %exn) nounwind
  %tmp3 = bitcast i8* %tmp2 to double*
  %exn.scalar11 = load double* %tmp3, align 8
  %call21 = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([18 x i8]* @.str1, i64 0, i64 0),
                                              double %exn.scalar11) optsize
  tail call void @__cxa_end_catch() nounwind
  br label %try.cont

try.cont:
  ret i32 undef

lpad:
  %exn.val = landingpad { i8*, i32 } personality i32 (...)* @__gxx_personality_v0
               catch i8** @_ZTIi, i8** @_ZTId
               filter i8** @_ZTIPKc
  %exn = extractvalue { i8*, i32 } %exn.val, 0
  %sel = extractvalue { i8*, i32 } %exn.val, 1
  %tmp4 = tail call i32 @llvm.eh.typeid.for(i8* bitcast (i8** @_ZTIi to i8*)) nounwind
  %tmp5 = icmp eq i32 %sel, %tmp4
  br i1 %tmp5, label %invoke.cont7, label %eh.next

eh.next:
  %tmp6 = tail call i32 @llvm.eh.typeid.for(i8* bitcast (i8** @_ZTId to i8*)) nounwind
  %tmp7 = icmp eq i32 %sel, %tmp6
  br i1 %tmp7, label %invoke.cont20, label %eh.next1

eh.next1:
  %ehspec.fails = icmp slt i32 %sel, 0
  br i1 %ehspec.fails, label %ehspec.unexpected, label %eh.resume

eh.resume:
  resume { i8*, i32 } %exn.val

ehspec.unexpected:
  tail call void @__cxa_call_unexpected(i8* %exn) noreturn
  unreachable
}


2) An example of inlining:

void qux();

void bar() __attribute__((always_inline));
void bar() {
  try {
    qux();
  } catch (char c) {
    printf("caught char %c\n", c);
  }
}

void foo() throw (const char *) {
  try {
    bar();
  } catch (int i) {
    printf("caught integer %d\n", i);
  } catch (double d) {
    printf("caught double %g\n", d);
  }
}

Produces (see comments inline):

@_ZTIc = external constant i8*
@.str = private unnamed_addr constant [16 x i8] c"caught char %c\0A\00", align 1
@_ZTIPKc = external constant i8*
@_ZTIi = external constant i8*
@_ZTId = external constant i8*
@.str1 = private unnamed_addr constant [19 x i8] c"caught integer %d\0A\00", align 1
@.str2 = private unnamed_addr constant [18 x i8] c"caught double %g\0A\00", align 1

define void @_Z3barv() uwtable optsize alwaysinline ssp {
entry:
  invoke void @_Z3quxv() optsize
          to label %try.cont unwind label %lpad

try.cont:
  ret void

lpad:
  %exn.val = landingpad { i8*, i32 } personality i32 (...)* @__gxx_personality_v0
               catch i8** @_ZTIc
  %exn = extractvalue { i8*, i32 } %exn.val, 0
  %sel = extractvalue { i8*, i32 } %exn.val, 1
  %tmp2 = tail call i32 @llvm.eh.typeid.for(i8* bitcast (i8** @_ZTIc to i8*)) nounwind
  %tmp3 = icmp eq i32 %sel, %tmp2
  br i1 %tmp3, label %invoke.cont4, label %eh.resume

invoke.cont4:
  %tmp0 = tail call i8* @__cxa_begin_catch(i8* %exn) nounwind
  %exn.scalar = load i8* %tmp0, align 1
  %conv = sext i8 %exn.scalar to i32
  %call = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([16 x i8]* @.str, i64 0, i64 0), i32 %conv) optsize
  tail call void @__cxa_end_catch() nounwind
  br label %try.cont

eh.resume:
  resume { i8*, i32 } %exn.val
}

define i32 @_Z3foov() uwtable optsize ssp {
entry:
  invoke void @_Z3quxv() optsize
          to label %try.cont.bar unwind label %lpad.bar

try.cont.bar:
  ret void

lpad.bar:
  ;; bar's landing pad is updated to indicate that it can catch an int or double
  ;; exception.
  %exn.val.bar = landingpad { i8*, i32 } personality i32 (...)* @__gxx_personality_v0
               catch i8** @_ZTIc, i8** @_ZTIi, i8** @_ZTId
  %exn.bar = extractvalue { i8*, i32 } %exn.val.bar, 0
  %sel.bar = extractvalue { i8*, i32 } %exn.val.bar, 1
  %tmp2.bar = tail call i32 @llvm.eh.typeid.for(i8* bitcast (i8** @_ZTIc to i8*)) nounwind
  %tmp3.bar = icmp eq i32 %sel, %tmp2.bar
  br i1 %tmp3.bar, label %invoke.cont4.bar, label %eh.resume.bar

invoke.cont4.bar:
  %tmp0.bar = tail call i8* @__cxa_begin_catch(i8* %exn) nounwind
  %exn.scalar.bar = load i8* %tmp0.bar, align 1
  %conv.bar = sext i8 %exn.scalar.bar to i32
  %call.bar = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([16 x i8]* @.str, i64 0, i64 0), i32 %conv.bar) optsize
  tail call void @__cxa_end_catch() nounwind
  br label %try.cont.bar

eh.resume.bar:
  ;; bar's 'resume' instruction was replaced by an unconditional branch to the
  ;; foo's landing pad.
  br label %lpad.split

invoke.cont7:
  %tmp0 = tail call i8* @__cxa_begin_catch(i8* %exn) nounwind
  %tmp1 = bitcast i8* %tmp0 to i32*
  %exn.scalar = load i32* %tmp1, align 4
  %call = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([19 x i8]* @.str, i64 0, i64 0),
                                            i32 %exn.scalar) optsize
  tail call void @__cxa_end_catch() nounwind
  br label %try.cont

invoke.cont20:
  %tmp2 = tail call i8* @__cxa_begin_catch(i8* %exn) nounwind
  %tmp3 = bitcast i8* %tmp2 to double*
  %exn.scalar11 = load double* %tmp3, align 8
  %call21 = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([18 x i8]* @.str1, i64 0, i64 0),
                                              double %exn.scalar11) optsize
  tail call void @__cxa_end_catch() nounwind
  br label %try.cont

try.cont:
  ret i32 undef

lpad:
  %exn.val = landingpad { i8*, i32 } personality i32 (...)* @__gxx_personality_v0
               catch i8** @_ZTIi, i8** @_ZTId
               filter i8** @_ZTIPKc
  br label %lpad.split

lpad.split:
  ;; foo's landing pad is split after the 'landingpad' instruction so that bar's
  ;; 'landingpad' can continue processing the exception if it wasn't handled in
  ;; bar.
  %exn.val.phi = phi { i8*, i32 } [ %exn.val, %lpad ], [ %exn.val.bar, %lpad.bar ]
  %exn = extractvalue { i8*, i32 } %exn.val.phi, 0
  %sel = extractvalue { i8*, i32 } %exn.val.phi, 1
  %tmp4 = tail call i32 @llvm.eh.typeid.for(i8* bitcast (i8** @_ZTIi to i8*)) nounwind
  %tmp5 = icmp eq i32 %sel, %tmp4
  br i1 %tmp5, label %invoke.cont7, label %eh.next

eh.next:
  %tmp6 = tail call i32 @llvm.eh.typeid.for(i8* bitcast (i8** @_ZTId to i8*)) nounwind
  %tmp7 = icmp eq i32 %sel, %tmp6
  br i1 %tmp7, label %invoke.cont20, label %eh.next1

eh.next1:
  %ehspec.fails = icmp slt i32 %sel, 0
  br i1 %ehspec.fails, label %ehspec.unexpected, label %eh.resume

eh.resume:
  resume { i8*, i32 } %exn.val.phi

ehspec.unexpected:
  tail call void @__cxa_call_unexpected(i8* %exn) noreturn
  unreachable
}




More information about the llvm-dev mailing list