RFC: Exception Handling Proposal Revised

Hi Bill,

3. In onto.catch.handlers you resume to %lpad. Which basic-block is
this? Shouldn't this be a "resume_unwinding" call of some sort (if Q2
is right)?

This was necessary to keep the invariant that only resume or unwind edges may
jump to a landing pad (%lpad in this instance). Otherwise, I would have the
invoke in a.dtor to directly to %lpad on the "normal" edge.

I couldn't find any basic block labelled "%lpad". I think this is one of
the things Renato was pointing out.

Ciao,

Duncan.

Hi Bill, this proposal seems strangely complicated. I don’t see the advantage
of the dispatch instruction over just attaching the information to each invoke.
Right now you have

invoke void @_Z3foov()
to label %invcont unwind label %catch.handlers

catch.handlers: landingpad
dispatch resume to label %…
catches [
%struct.__fundamental_type_info_pseudo* @_ZTIi, label %ch.int
%struct.__pointer_type_info_pseudo* @_ZTIPKc, label %ch.str
]
catchall [i8* null, label % [ch.ca](http://ch.ca)]

Why not combine this into:

invoke void @_Z3foov()
to label %invcont unwind resume to label %…
catches [
%struct.__fundamental_type_info_pseudo* @_ZTIi, label %ch.int
%struct.__pointer_type_info_pseudo* @_ZTIPKc, label %ch.str
]
catchall [i8* null, label % [ch.ca](http://ch.ca)]

? That would get rid of the “landingpad” basic block attribute, and rules about
what can branch to landing pads, that these cannot be split etc. Your rules
mean that you can uniquely associate a dispatch instruction with each invoke
unwind edge, so why not directly attach the dispatch information to the edge,
i.e. to the invoke?

That was my first proposal (way back when). I ran into the problem of cleanups. Basically, they get in the way of the decision making process. Consider this:

invoke void @_Z3foov()
to label %invcont unwind resume to label %…
catches [
%struct.__fundamental_type_info_pseudo* @_ZTIi, label %ch.int
%struct.__pointer_type_info_pseudo* @_ZTIPKc, label %ch.str
]
catchall [i8* null, label % [ch.ca](http://ch.ca)]

Now consider that a cleanup must occur here. How do we represent this here and still keep the information that “int” goes to ch.int and “const char *” goes to ch.str? The cleanups must happen before we get to ch.int, ch.str, and ch.ca. And cleanup code can be arbitrarily complex – including the fact that it may not return, or it may have multiple exit points. You end up needing a dispatch-like instruction at the end of the cleanup region so that you can associated the invoke to the decision point – the point where we determine which handler we need to execute. After we realized this, this proposal became the natural way.

Syntax:

dispatch resume to label

How do you say: if nothing is matched, keep unwinding out of the function?
Actually I don’t see why is useful at all, since you can always
place a complete set of all handlers into one dispatch, so redispatching is
not needed. That said, it might be handy for reducing code duplication.

The will have code to handling unwinding out of the function. Normally, it will just call _Unwind_Resume(). As you saw in the example, though, it may call other things (like `terminate()’). I would love to have a language-neutral way of saying “resume unwinding”. I don’t know if it’s sufficient to rely upon every implementation to call _Unwind_Resume().

catches [

, label

]

catchall [ , label ]

personality [ ]

filters [

, , …

You also need labels for filters. I know it may not look like it, but
if you inline a function with a filter into a function with handlers
then the filter ends up being nested inside outer handlers, resulting
in the need for a destination. In fact inlining also means that you
can’t just list filters after catches, you need to consider maybe
having some catches, then a filter, then some other catches etc. Or
perhaps you want to take care of it by chaining dispatches via the
resumedest?

When I looked at code which inlined functions with filters, it seemed like they were attached to the throwing instructions. In that case, you will have the dispatch of the inlined function, which will be associated with its invokes, and the filter information will exist there. In that way, we have the “nesting” that you described.

]

The catches', catchall’, and filters' clauses are optional. If neither catches’ nor `catchall’ is specified, then the landing pad is implicitly a cleanup.

You may have cleanups to run even if there are handlers, how do you distinguish
between “have handlers, plus a cleanup to run in any case” and “have handlers,
no cleanup needs to be run if handlers don’t match, just unwind straight
through”?

The first case is my example. E.g.:

lpad: landingpad
call void @cleanup(%A* %a)
dispatch resume to label %unwind
catches [ . . . ]
etc.

The second one would be the job of the front-end to generate the correct dispatch. E.g:

lpad: landingpad
dispatch resume to label %unwind
catches [ . . . ]

• The `filters’ clause lists the types of exceptions which may be thrown by the

region.

What is “a region”?

I defined it in the first document, which was referenced:

A "region" is defined as a section of code where, if an exception is thrown
anywhere within that section, control is passed to code that will handle the
exception (called a "landing pad"). The code which handles the specific
exception is unique to that region.

onto.catch.handlers:

dispatch resume to %lpad

What is %lpad?

A typo. It should be %catch.handlers

As a general remark, I don’t see how this is superior to gcc’s region scheme,
which could be adapted to LLVM by (in essence) attaching to an invoke the list
of all regions (and their info like list of catches) that contain the invoke.
If you do that then you get something rather close to your proposal, only the
dispatch instructions have become part of the invokes, you don’t need the funny
landing pad special semantics etc.

Could you describe GCC’s region scheme?

-bw

Ah! That explains it. :slight_smile:

Yes, they aren’t baked in. You may run the cleanups whenever you wish. Or indeed not at all if you desire. :slight_smile:

-bw

Hi Bill,

Why not combine this into:

invoke void @_Z3foov()
to label %invcont unwind resume to label %...
catches [
%struct.__fundamental_type_info_pseudo* @_ZTIi, label %ch.int
%struct.__pointer_type_info_pseudo* @_ZTIPKc, label %ch.str
]
catchall [i8* null, label % ch.ca <http://ch.ca>]

? That would get rid of the "landingpad" basic block attribute, and rules about
what can branch to landing pads, that these cannot be split etc. Your rules
mean that you can uniquely associate a dispatch instruction with each invoke
unwind edge, so why not directly attach the dispatch information to the edge,
i.e. to the invoke?

That was my first proposal (way back when). I ran into the problem of cleanups.
Basically, they get in the way of the decision making process. Consider this:

invoke void @_Z3foov()
to label %invcont unwind resume to label %...
catches [
%struct.__fundamental_type_info_pseudo* @_ZTIi, label %ch.int
%struct.__pointer_type_info_pseudo* @_ZTIPKc, label %ch.str
]
catchall [i8* null, label % ch.ca <http://ch.ca>]

Now consider that a cleanup must occur here. How do we represent this here and
still keep the information that "int" goes to ch.int and "const char *" goes to
ch.str? The cleanups must happen before we get to ch.int, ch.str, and ch.ca
<http://ch.ca>. And cleanup code can be arbitrarily complex – including the fact
that it may not return, or it may have multiple exit points. You end up needing
a dispatch-like instruction at the end of the cleanup region so that you can
associated the invoke to the decision point – the point where we determine which
handler we need to execute. After we realized this, this proposal became the
natural way.

thanks for the explanation. I am writing up an alternative exception handling
proposal which does not suffer from this problem (at least I think it doesn't!).

As a general remark, I don't see how this is superior to gcc's region scheme,
which could be adapted to LLVM by (in essence) attaching to an invoke the list
of all regions (and their info like list of catches) that contain the invoke.
If you do that then you get something rather close to your proposal, only the
dispatch instructions have become part of the invokes, you don't need the funny
landing pad special semantics etc.

Could you describe GCC's region scheme?

My alternative proposal will describe the scheme you get if you do what the
above paragraph suggests. I don't plan to explicitly describe gcc's nested
region scheme, but rest assured that in llvm-gcc it would be easy to
implement - in some ways easier than what we do now.

Ciao,

Duncan.

Hi Duncan,

Remember that a big reason why to change was to support instructions
that could throw exceptions (such as divide in Java). The idea of
having all those dispatch/landingpad was to ultimately get rid of the
invoke instruction and move the information to the basic block, so
every instruction (including function calls) could, in theory, throw
an exception.

In this case, it'd be easier for the back-end to generate the EH table
regions, as it wouldn't need to consider invokes, only groups of basic
blocks (Bill's regions).

cheers,
--renato

Hi Renato,

Remember that a big reason why to change was to support instructions
that could throw exceptions (such as divide in Java). The idea of
having all those dispatch/landingpad was to ultimately get rid of the
invoke instruction and move the information to the basic block, so
every instruction (including function calls) could, in theory, throw
an exception.

In this case, it'd be easier for the back-end to generate the EH table
regions, as it wouldn't need to consider invokes, only groups of basic
blocks (Bill's regions).

this consideration is orthogonal to my proposal as it is to Bill's.

Ciao,

Duncan.

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 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