[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