How to code catch-all in the new exception handling scheme?

I’m looking at the docs, and while it refers to a “catch-all” clause, it doesn’t describe how to construct one. In the old scheme, a selector value of 0 was used. What’s the corresponding technique for the new scheme? Do I need to update my personality function to handle catch-alls (it wasn’t needed in the previous scheme)?

Hi Talin,

I'm looking at the docs, and while it refers to a "catch-all" clause,

hopefully Bill will get rid of the first reference to "catch-all" since
that section is inaccurate.

  it doesn't

describe how to construct one.

There is in general no such thing as a catch-all: it depends on the personality
function (i.e. the language), and, for example, the Ada language doesn't have
one (it has something that catches all Ada exceptions, but it won't catch C++
exceptions).

  In the old scheme, a selector value of 0 was

used.

Technically speaking that's a cleanup not a catch-all. C++ illustrates the
difference: if you throw an exception in a C++ program that only has cleanups
then the exception won't be unwound, instead the C++ unwinder code will just
terminate your program; however if there is a C++ catch-all (represented by
a catch with a null pointer in LLVM) then the exception will be unwound and
the landing pad branched to in the expected way. The C++ catch-all is
represented by a catch clause with a null pointer argument in LLVM, but this
is C++ specific. Note that the cleanup behaviour I mentioned is somewhat
built into the GCC unwinding library: when you call _Unwind_RaiseException,
if the search phase discovers that nothing matches the exception except for
cleanups all the way up the stack, then the call to _Unwind_RaiseException
returns (otherwise control branches to the landing pad that matched the
exception). If you want cleanups to be run anyway, then you need to call
_Unwind_ForcedUnwind if _Unwind_RaiseException returns. This is in contrast
to what happens if there is a catch-all: the call to _Unwind_RaiseException
will see that the exception matches the catch-all (because the personality
function says so) and it will cause execution to resume at the corresponding
landing pad.

  What's the corresponding technique for the new scheme?

To get a cleanup add the "cleanup" flag to the landingpad instruction. That
said, I'm ruminating on whether the cleanup flag should be removed: instead
there would always be an implicit cleanup. As an optimization, the code
generators would not output a cleanup into the exception handling tables if
no useful code is executed when the cleanup is run. This seems to be what
recent versions of GCC do.

  Do I need to update

my personality function to handle catch-alls (it wasn't needed in the previous
scheme)?

Ciao, Duncan.

Hi Talin,

I'm looking at the docs, and while it refers to a "catch-all" clause,

hopefully Bill will get rid of the first reference to "catch-all" since
that section is inaccurate.

I *think* this is now correct. Please check. :slight_smile:

it doesn't

describe how to construct one.

There is in general no such thing as a catch-all: it depends on the personality
function (i.e. the language), and, for example, the Ada language doesn't have
one (it has something that catches all Ada exceptions, but it won't catch C++
exceptions).

This is true in a sense that a catch-all is encoded the same way as any other catch type. So for C++, you would specify a catch-all like this:

%exn = landingpad { i8*, i32 } personality i32 (...)* __gxx_personality_v0
           catch i8* @_ZTIi
           catch i8* null

A 'null' value is C++'s way of saying "this is a catch-all". Ada uses a global variable (function?) for this. To the new EH scheme, they are indistinguishable from a "normal" catch.

To get a cleanup add the "cleanup" flag to the landingpad instruction. That
said, I'm ruminating on whether the cleanup flag should be removed: instead
there would always be an implicit cleanup. As an optimization, the code
generators would not output a cleanup into the exception handling tables if
no useful code is executed when the cleanup is run. This seems to be what
recent versions of GCC do.

I'm not a big fan of implicit behavior. :slight_smile: And it requires an optimization to "cleanup" (yeah, a pun...sue me) the extraneous code we generate, which won't happen at -O0 (right?). Though the optimization you mentioned here would be a nice thing to have with our current scheme.

-bw

Hi Bill,

I'm looking at the docs, and while it refers to a "catch-all" clause,

hopefully Bill will get rid of the first reference to "catch-all" since
that section is inaccurate.

I *think* this is now correct. Please check. :slight_smile:

I still have some niggles:

   The unwinder delegates the decision of whether to stop in a call frame to
   that call frame's language-specific personality function. Not all personality
   functions guarantee that they will stop to perform cleanups.

This is incorrect: it is not the personality function that makes the decision,
it is who-ever is doing the unwinding. For example if you use the Ada "throw"
method it will always run all C++ cleanups, even if that's all there is to do.
While if you use the C++ throw method it won't bother running Ada cleanups if
that is all there is to do. All personality functions that I am familiar with
treat cleanups in the same way.

   For example, the GNU C++ personality function doesn't do so unless the
   exception is actually caught somewhere further up the stack.

As mentioned above, this has nothing to do with the personality function.

   When using this personality to implement EH for a language that guarantees
   that cleanups will always be run (e.g. Ada), be sure to indicate a catch-all
   in the landingpad instruction rather than just cleanups.

I suggest you just delete this last bit. This isn't how Ada does it itself for
example.

it doesn't

describe how to construct one.

There is in general no such thing as a catch-all: it depends on the personality
function (i.e. the language), and, for example, the Ada language doesn't have
one (it has something that catches all Ada exceptions, but it won't catch C++
exceptions).

This is true in a sense that a catch-all is encoded the same way as any other catch type. So for C++, you would specify a catch-all like this:

  %exn = landingpad { i8*, i32 } personality i32 (...)* __gxx_personality_v0
            catch i8* @_ZTIi
            catch i8* null

A 'null' value is C++'s way of saying "this is a catch-all". Ada uses a global variable (function?) for this.

Yes, Ada has a global variable that plays this role. However calling it
"catch-all" is misleading since it doesn't match any C++ exceptions (unlike
the C++ catch-all which also matches foreign exceptions). This means that
the old exception handling scheme wouldn't always work properly if C++ code
was inlined into Ada code. Not a big deal since it wouldn't work properly
anyway for all kinds of other reasons (eg: multiple personality functions in
the same function).

  To the new EH scheme, they are indistinguishable from a "normal" catch.

To get a cleanup add the "cleanup" flag to the landingpad instruction. That
said, I'm ruminating on whether the cleanup flag should be removed: instead
there would always be an implicit cleanup. As an optimization, the code
generators would not output a cleanup into the exception handling tables if
no useful code is executed when the cleanup is run. This seems to be what
recent versions of GCC do.

I'm not a big fan of implicit behavior. :slight_smile:

It would be explicitly documented, so then it wouldn't be implicit! In fact
it would mean that "invoke" has the traditional LLVM semantics: if an exception
is unwound then control branches to the landing pad. With the new scheme that
is currently no longer so - meaning that some of the PruneEH logic is currently
wrong :slight_smile: That said, I'm also a bit dubious about this "always has a cleanup"
idea.

  And it requires an optimization to "cleanup" (yeah, a pun...sue me) the extraneous code we generate, which won't happen at -O0 (right?).

The only effect of this optimization would be to not output a cleanup entry (0)
in the action table if its not needed. That's pretty mild, and at -O0 who cares
if there are some pointless but harmless (except for slowing down unwinding)
cleanup entries in the action table? I'm not sure what you mean by "the
extraneous code we generate".

  Though the optimization you mentioned here would be a nice thing to have with our current scheme.

My plan is to implement the optimization (clearing of the cleanup flag on the
landingpad instruction) at the IR level as a warm-up. If it seems like a good
idea to eliminate the cleanup flag from the definition of landingpad instruction
then the logic can be moved to a codegen analysis instead, and used to see if
we can avoid adding a cleanup entry to the landing pad table. Note that I
already implemented the optimization that if all a landing pad does is call
"resume", then the invoke+landingpad is removed in favour of a call, in case
that is what you are thinking of.

Ciao, Duncan.

Hi Duncan,

Hi Bill,

I'm looking at the docs, and while it refers to a "catch-all" clause,

hopefully Bill will get rid of the first reference to "catch-all" since
that section is inaccurate.

I *think* this is now correct. Please check. :slight_smile:

I still have some niggles:

  The unwinder delegates the decision of whether to stop in a call frame to
  that call frame's language-specific personality function. Not all personality
  functions guarantee that they will stop to perform cleanups.

This is incorrect: it is not the personality function that makes the decision,
it is who-ever is doing the unwinding. For example if you use the Ada "throw"
method it will always run all C++ cleanups, even if that's all there is to do.
While if you use the C++ throw method it won't bother running Ada cleanups if
that is all there is to do. All personality functions that I am familiar with
treat cleanups in the same way.

How does one incorporate their own LLVM backend unwinding mechanism?
I'm assuming that you have an Ada backend. Does your backend not call
_Unwind_RaiseException for example?

[snip]

...

Ciao, Duncan.
_______________________________________________
LLVM Developers mailing list
LLVMdev@cs.uiuc.edu http://llvm.cs.uiuc.edu
http://lists.cs.uiuc.edu/mailman/listinfo/llvmdev

Thanks in advance

Garrison

Hi Bill,

I'm looking at the docs, and while it refers to a "catch-all" clause,

hopefully Bill will get rid of the first reference to "catch-all" since
that section is inaccurate.

I *think* this is now correct. Please check. :slight_smile:

I still have some niggles:

The unwinder delegates the decision of whether to stop in a call frame to
that call frame's language-specific personality function. Not all personality
functions guarantee that they will stop to perform cleanups.

This is incorrect: it is not the personality function that makes the decision,
it is who-ever is doing the unwinding. For example if you use the Ada "throw"
method it will always run all C++ cleanups, even if that's all there is to do.
While if you use the C++ throw method it won't bother running Ada cleanups if
that is all there is to do. All personality functions that I am familiar with
treat cleanups in the same way.

Wait...the personality function is the bit of code that finds the handler and tells the unwinder about it. The unwinder, at least the C++ unwinder, calls the personality function in phase 2 telling it that it's in the "cleanup" phase. If it's not at the handler frame, then the personality will install the context and switch to that landing pad. The Ada unwinder (at least in 4.2) is much simpler, and will install the context in phase 2 if it finds anything that it should execute.

In your example, the C++ personality function doesn't know about Ada exceptions. All it knows is that there are no catches for the exception, and thus calls terminate on its own. I don't know about Ada, but perhaps it lies to the C++ personality and tells it that there is a handler above it?

For example, the GNU C++ personality function doesn't do so unless the
exception is actually caught somewhere further up the stack.

As mentioned above, this has nothing to do with the personality function.

When using this personality to implement EH for a language that guarantees
that cleanups will always be run (e.g. Ada), be sure to indicate a catch-all
in the landingpad instruction rather than just cleanups.

I suggest you just delete this last bit. This isn't how Ada does it itself for
example.

Sure.

To get a cleanup add the "cleanup" flag to the landingpad instruction. That
said, I'm ruminating on whether the cleanup flag should be removed: instead
there would always be an implicit cleanup. As an optimization, the code
generators would not output a cleanup into the exception handling tables if
no useful code is executed when the cleanup is run. This seems to be what
recent versions of GCC do.

I'm not a big fan of implicit behavior. :slight_smile:

It would be explicitly documented, so then it wouldn't be implicit! In fact
it would mean that "invoke" has the traditional LLVM semantics: if an exception
is unwound then control branches to the landing pad. With the new scheme that
is currently no longer so - meaning that some of the PruneEH logic is currently
wrong :slight_smile: That said, I'm also a bit dubious about this "always has a cleanup"
idea.

The traditional LLVM semantics of exception handling were broken. I don't want to go back to them. :slight_smile:

And it requires an optimization to "cleanup" (yeah, a pun...sue me) the extraneous code we generate, which won't happen at -O0 (right?).

The only effect of this optimization would be to not output a cleanup entry (0)
in the action table if its not needed. That's pretty mild, and at -O0 who cares
if there are some pointless but harmless (except for slowing down unwinding)
cleanup entries in the action table? I'm not sure what you mean by "the
extraneous code we generate".

Extra entries in the tables when we don't need them. Possibly slowing down unwinding because it's stopping at functions which have no cleanups and don't catch. Even at O0 we don't want to penalize people.

Hi Garrison,

   The unwinder delegates the decision of whether to stop in a call frame to
   that call frame's language-specific personality function. Not all personality
   functions guarantee that they will stop to perform cleanups.

This is incorrect: it is not the personality function that makes the decision,
it is who-ever is doing the unwinding. For example if you use the Ada "throw"
method it will always run all C++ cleanups, even if that's all there is to do.
While if you use the C++ throw method it won't bother running Ada cleanups if
that is all there is to do. All personality functions that I am familiar with
treat cleanups in the same way.

How does one incorporate their own LLVM backend unwinding mechanism?
I'm assuming that you have an Ada backend. Does your backend not call
_Unwind_RaiseException for example?

it does, and follows that call with a call to _Unwind_ForcedUnwind. If only
cleanups are seen up the call stack then the call to _Unwind_RaiseException
returns. The call to _Unwind_ForcedUnwind then runs the cleanups. When (if)
it returns then the program is terminated. C++ skips the call to
_Unwind_ForcedUnwind and just terminates the program at once if
_Unwind_RaiseException returns.

Ciao, Duncan.

Hi Bill,

  The unwinder delegates the decision of whether to stop in a call frame to
  that call frame's language-specific personality function. Not all personality
  functions guarantee that they will stop to perform cleanups.

I was talking about who decides what to do if there are only cleanups all the
way up the stack (in C++ the program is terminated without running the cleanups;
in Ada the program is terminated after running the cleanups). The language
"throw" routine decides this, not the personality function (the personality
function says whether there is a cleanup in a given frame; using this info
for each call frame the unwinder can tell if there are only cleanups all the
way up the stack, or if there is a real handler somewhere; the throw routine
decides what to do based on what the unwinder tells it). As far as I know
all personality functions treat cleanups the same.

This is incorrect: it is not the personality function that makes the decision,
it is who-ever is doing the unwinding. For example if you use the Ada "throw"
method it will always run all C++ cleanups, even if that's all there is to do.
While if you use the C++ throw method it won't bother running Ada cleanups if
that is all there is to do. All personality functions that I am familiar with
treat cleanups in the same way.

Wait...the personality function is the bit of code that finds the handler and tells the unwinder about it. The unwinder, at least the C++ unwinder, calls the personality function in phase 2 telling it that it's in the "cleanup" phase.

Not so! If only cleanups are found in phase 1 then the unwinder hits the top
of the stack without finding a handler and returns _URC_END_OF_STACK without
installing any handlers; note that a cleanup is not considered to be a handler.
If the C++ unwinder sees the call to _Unwind_RaiseException return like this
then it terminates the program. Cleanups will therefore not have been run.
The Ada unwinder behaves differently. If it the call to _Unwind_RaiseException
returns then it calls _Unwind_ForcedUnwind which runs all the cleanups. The
program is then terminated. In the more common situation of the unwinder seeing
a handler somewhere up the stack it doesn't return and moves on to phase 2 as
you described; but your description doesn't apply to the case in which there are
only cleanups.

  If it's not at the handler frame, then the personality will install the context and switch to that landing pad. The Ada unwinder (at least in 4.2) is much simpler, and will install the context in phase 2 if it finds anything that it should execute.

In your example, the C++ personality function doesn't know about Ada exceptions. All it knows is that there are no catches for the exception, and thus calls terminate on its own.

Nope. First off, the C++ personality function does have special support for
foreign exceptions. A C++ catch-all will happily catch Ada exceptions for
example. If there is a C++ cleanup and an Ada exception is unwinding then
(assuming the Ada exception isn't matched by a C++ catch-all) the C++
personality function acts exactly the same as if a C++ exception was unwinding:
it returns _URC_CONTINUE_UNWIND and the unwinder continues to unwind. Let's
suppose that there are only cleanups all the way up the stack. Then phase 1
ends with _Unwind_RaiseException returning _URC_END_OF_STACK. If it was C++'s
"throw" that was doing the unwinding, it will terminate the program at this
point. If it was Ada's "raise" that was doing the unwinding, it will call
_Unwind_ForcedUnwind which will run all cleanups including the C++ ones. Then
it will terminate the program.

  I don't know about Ada, but perhaps it lies to the C++ personality and tells it that there is a handler above it?

Nope.

The traditional LLVM semantics of exception handling were broken. I don't want to go back to them. :slight_smile:

They weren't really broken, it was the inliner that was using them wrong. The
subtlety is that *if* an exception is unwound (by this I mean *phase 2*, i.e.
the actual running of handlers and cleanups) *then* indeed it was true that
control branched to the landing pad. But what was forgotten is that whoever
is doing the unwinding might make its decision as to whether to unwind or not
(i.e. perform phase 2) based on what it sees up the stack. By rearranging the
set of handlers etc seen up the stack, the old inliner could change the decision
made by the unwinder, changing program behaviour. More concretely, the old
inliner could change from a situation in which the unwinder saw a handler to a
situation in which the unwinder saw only cleanups; in the second situation the
C++ unwinder would terminate the program - very bad. [But if the unwinder had
decided to proceed to phase 2 then indeed control would branch to the landing
pad as the LLVM semantics specified]. The main point of the new eh scheme
(from my point of view) is that the inliner no longer changes the list of
handlers and other clauses seen by the unwinder when it looks up the stack, and
thus the unwinder makes the same decision as to whether to proceed or not to
stage 2 regardless of whether inlining occurred or not.

I don't see anything wrong with having the "old" semantics that *if* an
exception is unwound then control always branches to the landing pad. It
seems like a slight simplification to me (not clear it really buys anything).
It should of course be documented that the unwinder might choose not to unwind
at all (i.e. not proceed to phase 2) if the exception does match any landingpad
clauses. Maybe this is too subtle and would just confuse everyone.

And it requires an optimization to "cleanup" (yeah, a pun...sue me) the extraneous code we generate, which won't happen at -O0 (right?).

The only effect of this optimization would be to not output a cleanup entry (0)
in the action table if its not needed. That's pretty mild, and at -O0 who cares
if there are some pointless but harmless (except for slowing down unwinding)
cleanup entries in the action table? I'm not sure what you mean by "the
extraneous code we generate".

Extra entries in the tables when we don't need them. Possibly slowing down unwinding because it's stopping at functions which have no cleanups and don't catch. Even at O0 we don't want to penalize people.

I find it hard to take this argument seriously. How many people care about the
speed of exception unwinding? Almost no one. How many of those are going to
be upset because unwinding is slow at -O0? Surely none: at -O0 I'm sure that
exception unwinding speed will be the least of their speed issues.

Ciao, Duncan.

Bill, and Duncan forced me to go back though my code, and the Itanium exception
ABI doc: http://sourcery.mentor.com/public/cxx-abi/abi-eh.html. For the clarity of
others, and for recorded posterity, I am tagging a couple of Duncan's statements
with references back to the doc which is now at rev 1.22.

[snip]

If only cleanups are found in phase 1 then the unwinder hits the top
of the stack without finding a handler and returns _URC_END_OF_STACK without
installing any handlers; note that a cleanup is not considered to be a handler.
If the C++ unwinder sees the call to _Unwind_RaiseException return like this
then it terminates the program. Cleanups will therefore not have been run.
The Ada unwinder behaves differently. If it the call to _Unwind_RaiseException
returns then it calls _Unwind_ForcedUnwind which runs all the cleanups. The
program is then terminated. In the more common situation of the unwinder seeing
a handler somewhere up the stack it doesn't return and moves on to phase 2 as
you described; but your description doesn't apply to the case in which there are
only cleanups.

Under section 1.3 the _Unwind_RaiseException function can return:

Hi Bill,

The unwinder delegates the decision of whether to stop in a call frame to
that call frame's language-specific personality function. Not all personality
functions guarantee that they will stop to perform cleanups.

I was talking about who decides what to do if there are only cleanups all the
way up the stack (in C++ the program is terminated without running the cleanups;
in Ada the program is terminated after running the cleanups). The language
"throw" routine decides this, not the personality function (the personality
function says whether there is a cleanup in a given frame; using this info
for each call frame the unwinder can tell if there are only cleanups all the
way up the stack, or if there is a real handler somewhere; the throw routine
decides what to do based on what the unwinder tells it). As far as I know
all personality functions treat cleanups the same.

This is incorrect: it is not the personality function that makes the decision,
it is who-ever is doing the unwinding. For example if you use the Ada "throw"
method it will always run all C++ cleanups, even if that's all there is to do.
While if you use the C++ throw method it won't bother running Ada cleanups if
that is all there is to do. All personality functions that I am familiar with
treat cleanups in the same way.

Wait...the personality function is the bit of code that finds the handler and tells the unwinder about it. The unwinder, at least the C++ unwinder, calls the personality function in phase 2 telling it that it's in the "cleanup" phase.

Not so!

Not according to the source code for the personality function and the unwind_phase2 that I read before posting the above comment. For the code in question (for the C++ personality function and Darwin's libunwind), libunwind does only what the personality function tells it to do. The personality function is in charge of calling 'std::terminate' even. I looked at Ada's personality function in gcc 4.2 and it's doing roughly the same thing.

To reiterate my point, the personality function is in charge of telling the unwinder what to do, not the other way around.

-bw