[LLVMdev] RFC: Exception Handling Rewrite
Garrison Venn
gvenn.cfe.dev at gmail.com
Sat Jul 23 02:36:51 PDT 2011
Hi Bill,
Thanks for working on this.
Is there a reference for the function attribute uwtable, or is it to be defined as
part of this effort?
Thanks in advance
Garrison
On Jul 23, 2011, at 1:29, Bill Wendling wrote:
> 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