Exception Handling Tables Question

I have a question concerning the exception handling tables that we generate.

Right now we generate call sites for all of the code in a function, even for code that cannot (or at least shouldn't) throw an exception. E.g., for this code:

#include <cstdio>

struct Cleanup {
    ~Cleanup(void) {
      printf("in cleanup\n");
    }
};

static void inline_me(void) {
    Cleanup C;
    throw 0;
}

int main(void) {
    try {
      inline_me();
    } catch (...) {
      printf("in catch all\n");
    }
    return 0;
}

(assume it's all been inlined), the EH handling table for "main" goes from Leh_func_begin1 to Leh_func_end1, marking off each region whether it can throw or not. From my reading of the Exception Tables documentation, this is unnecessary – at least for C++. If a call throws and it doesn't have an entry in the unwind table, then the "terminate()" function is called. Is it fair to assume that there is a similar mechanism for other languages? The reason to ask this is that it could make the EH tables smaller and less cluttered if we elide those areas which we know don't throw (the functions called are marked 'nounwind', etc.).

-bw

Hi Bill,

Right now we generate call sites for all of the code in a function, even for code that cannot (or at least shouldn't) throw an exception.

...

(assume it's all been inlined), the EH handling table for "main" goes from Leh_func_begin1 to Leh_func_end1, marking off each region whether it can throw or not. From my reading of the Exception Tables documentation, this is unnecessary – at least for C++. If a call throws and it doesn't have an entry in the unwind table, then the "terminate()" function is called. Is it fair to assume that there is a similar mechanism for other languages?

in Ada these extra table entries are not needed at all: whether you
have them or not, an exception will continue to unwind. There is no
calling of "terminate". In short: you only need a table entry for
invoke calls, i.e. things you want to catch in this function.

The reason to ask this is that

it could make the EH tables smaller and less cluttered if we elide those areas which we know don't throw (the functions called are marked 'nounwind', etc.).

Sure, that's what the SawPotentiallyThrowing boolean is for. It is
currently set for any call, but in fact needn't be set for 'nounwind'
calls. When I wrote this stuff 'nounwind' information wasn't available
at the codegen level, which is why this isn't done. In case you care,
doing this would not hurt Ada :slight_smile:

Ciao,

Duncan.

Yay! :slight_smile: I'm interested in getting the tables smaller. And it will
help debug when things go wrong. Thanks!

-bw

Hi Bill,

The reason to ask this is that

it could make the EH tables smaller and less cluttered if we elide those areas which we know don't throw (the functions called are marked 'nounwind', etc.).

Sure, that's what the SawPotentiallyThrowing boolean is for. It is
currently set for any call, but in fact needn't be set for 'nounwind'
calls. When I wrote this stuff 'nounwind' information wasn't available
at the codegen level, which is why this isn't done. In case you care,
doing this would not hurt Ada :slight_smile:

now I think about it further, I guess SawPotentiallyThrowing is not for
this exactly, you'll need to add some additional logic near this place.

Ciao,

Duncan.

Yeah. The logic will need tweaking for sure. I'm also concerned about the _Unwind_resume() call. GCC emits a call site region for it in the exception table. We...kind of do that. It looks like it's being included in one of the "this is a region which isn't in a try-catch block, but it has a call in it, so lets add it to the exception table" areas. If I implement my suggestion, this will likely go away. I think Eric's working on something that will add the _Unwind_resume() call during selection DAG time, where we can place EH labels around it. But it's just another thing I have to worry about when doing this. :slight_smile:

-bw

Hi Bill,

Yeah. The logic will need tweaking for sure. I'm also concerned about the _Unwind_resume() call. GCC emits a call site region for it in the exception table. We...kind of do that. It looks like it's being included in one of the "this is a region which isn't in a try-catch block, but it has a call in it, so lets add it to the exception table" areas.

isn't that exactly how it should be? This is a call that will unwind
out of the function, so C++ requires it to have a call site region just
like any other call that we want to let unwind out of the function. I
don't see why it needs any special logic. If I understood right the
change you want to make is that if a call is known not to unwind then
you want to omit adding a call-site entry if that saves some space in
the call site table, which seems irrelevant to this, or am I missing
something? By the way, LLVM "nounwind" calls are different to GCC no
throw regions IIRC. If an exception is thrown in a GCC no throw region
then it must (C++) result in a call to "terminate". These are not
mapped to "nounwind", instead we create explicit "catch-all" filter
expressions for this (IIRC). In LLVM it is undefined what happens if
a call is marked "nounwind" but nonetheless an exception unwinds out
of it. Thus you can add call-site entries for nounwind calls,
or not add them, as you like - whatever is most convenient (eg: saves
the most space). An interesting optimization which we don't do is to
identify which calls correspond to a "catch-all" filter and not generate
an entry for them in the call-site table (no need to add the filter
either) - this saves space and the C++ runtime knows to handle this
just like if we added the filter explicitly.

If I

implement my suggestion, this will likely go away. I think Eric's working on something that will add the _Unwind_resume() call during selection DAG time, where we can place EH labels around it. But it's just another thing I have to worry about when doing this. :slight_smile:

I don't see why _Unwind_resume needs special handling. That said,
I'm not opposed to having "unwind" be turned into a special SDAG node
which targets can handle specially. I didn't do that because it seemed
awkward and not that useful (since targets can already specify whether
they want to call _Unwind_Resume or something else).

Ciao,

Duncan.

There's a miscommunication here. :slight_smile: The _Unwind_resume call isn't
marked with "nounwind", however it's not called through an "invoke"
instruction, only a regular "call" instruction. From what I can see,
the only reason it falls within a call site in the exception table is
because we're generating call sites for areas of code without
"invokes". If I implement my optimization to eliminate these call site
entries which don't have "invoke" calls in them, then the
_Unwind_resume call won't have an entry into the exception table, and
that would be bad.

That was all I'm saying here.

-bw

Hi Bill,

Yeah. The logic will need tweaking for sure. I'm also concerned about the
_Unwind_resume() call. GCC emits a call site region for it in the exception
table. We...kind of do that. It looks like it's being included in one of the
"this is a region which isn't in a try-catch block, but it has a call in it,
so lets add it to the exception table" areas.

isn't that exactly how it should be? This is a call that will unwind
out of the function, so C++ requires it to have a call site region just
like any other call that we want to let unwind out of the function. I
don't see why it needs any special logic. If I understood right the
change you want to make is that if a call is known not to unwind then
you want to omit adding a call-site entry if that saves some space in
the call site table, which seems irrelevant to this, or am I missing
something? By the way, LLVM "nounwind" calls are different to GCC no
throw regions IIRC. If an exception is thrown in a GCC no throw region
then it must (C++) result in a call to "terminate". These are not
mapped to "nounwind", instead we create explicit "catch-all" filter
expressions for this (IIRC). In LLVM it is undefined what happens if
a call is marked "nounwind" but nonetheless an exception unwinds out
of it. Thus you can add call-site entries for nounwind calls,
or not add them, as you like - whatever is most convenient (eg: saves
the most space). An interesting optimization which we don't do is to
identify which calls correspond to a "catch-all" filter and not generate
an entry for them in the call-site table (no need to add the filter
either) - this saves space and the C++ runtime knows to handle this
just like if we added the filter explicitly.

There's a miscommunication here. :slight_smile: The _Unwind_resume call isn't
marked with "nounwind", however it's not called through an "invoke"
instruction, only a regular "call" instruction. From what I can see,
the only reason it falls within a call site in the exception table is
because we're generating call sites for areas of code without
"invokes".

yes, and that's absolutely the right thing to do! Due to a strange
design choice, if a call does *not* have an entry in the call-site
table, and the call unwinds, then the runtime calls std::terminate.
All calls that may throw (like _Unwind_Resume!) must have an entry
in the call-site table.

If I implement my optimization to eliminate these call site

entries which don't have "invoke" calls in them, then the
_Unwind_resume call won't have an entry into the exception table, and
that would be bad.

As explained above, you can only eliminate call-site entries for
nounwind calls. Having a call-site entry has nothing to do with
whether a call is an invoke or not, it is to do with whether the
call can throw an exception or not.

Ciao,

Duncan.

But _Unwind_Resume() is special. It never returns. It does not throw.

The purpose of _Unwind_Resume() is for when you have a function with a destructor that implicitly needs running during exception processing as the exception unwinds through the stack. The unwinder jumps into that function's landing pad where the compiler has laid down a call to the destructor followed by a call to _Unwind_Resume() which then continues where the unwinding left off in processing the exception.

Therefore, I don't see the point of having a call-site entry for the _Unwind_Resume() "call".

-Nick

Hi Nick,

But _Unwind_Resume() is special. It never returns. It does not throw.

I disagree - this depends on the system libgcc version. Starting from
gcc-4.3, mainline gcc uses _Unwind_GetCFA rather than _Unwind_GetIP in
uw_identify_context. So from gcc-4.3 onwards, libgcc will indeed ignore
any call-site entry covering the position of the _Unwind_Resume call, so
indeed it doesn't matter what you put there. However lots of systems
still use libgcc from gcc-4.2 or earlier (for example, all of the LLVM
nightly testers running linux), and for them it does matter what is in
the call site table: they need an entry appropriate for a "throwing
call". [To be honest I didn't test this, but since on such systems it
is perfectly possible to catch the exception "thrown" by _Unwind_Resume
by placing an appropriate entry in the call-site table, it seems that
the address of the call is being looked up in the usual way in the
table].

So for compatibility with systems with an older libgcc, it is important
to have a call-site entry for _Unwind_Resume, like we produce right now.

The purpose of _Unwind_Resume() is for when you have a function with a destructor that implicitly needs running during exception processing as the exception unwinds through the stack. The unwinder jumps into that function's landing pad where the compiler has laid down a call to the destructor followed by a call to _Unwind_Resume() which then continues where the unwinding left off in processing the exception.

Therefore, I don't see the point of having a call-site entry for the _Unwind_Resume() "call".

As I mentioned above, this behaviour can't be relied upon on linux at
least. However maybe it can be relied upon for darwin, since the CFA
change has been in Apple gcc for a long time now. If you want to
exploit this, it can be done in a generic way as part of a more general
optimization, as follows.

First, observe that it doesn't matter if you have a call-site entry
or not for the _Unwind_Resume call, since the call is never looked up
in the table. It may not be optimal to omit the call-site entry, since
if the preceding and following entries have the same catches and
landing pads, all three entries (i.e. preceding, _Unwind_Resume and
following) could be merged into one entry with an address range that
covers all three. This kind of merging logic already exists in
ComputeCallSiteTable, it just needs to be taught that it doesn't matter
whether _Unwind_Resume is in any particular call-site or not. What
needs to happen is that in this line
         SawPotentiallyThrowing |= MI->getDesc().isCall();
you shouldn't set SawPotentiallyThrowing to true if the call is to
_Unwind_Resume.

However, there is no need to special case _Unwind_Resume, since there
is a whole class of calls for which you can do the same: all calls with
the nounwind attribute. If such a call does throw an exception, that
results in undefined behaviour, so it is perfectly fine to not have a
call-site entry for it, or to have one, just like for _Unwind_Resume.
Thus I suggest adding a codegen flag to say whether a call is a
"nounwind" call. The above line can change to something like:

         SawPotentiallyThrowing |= (MI->getDesc().isCall() && !MI->getDesc().isNoUnwind);

The code in ComputeCallSiteTable will then compute a minimal set of
call-sites in a way that should make you happy :slight_smile:

In DwarfEHPrepare.cpp, if the platform is darwin then the calls to
_Unwind_Resume it generates can be marked "nounwind", and the rest
will happen automagically.

By the way, since nounwind calls occur a lot, this may result in
a big win in call-site table size, so I think it's worth doing
whether or not you do something special for _Unwind_Resume.

Ciao,

Duncan.

PS: I said to Bill that in fact SawPotentiallyThrowing was not what
he was looking for for nounwind calls, but I changed my mind again :slight_smile:

I was imprecise, but yes, I meant to say that we shouldn't have one for "nounwind" calls. :slight_smile:

-bw