[llvm-commits] [llvm-gcc-4.2] r99670 - /llvm-gcc-4.2/trunk/gcc/llvm-convert.cpp

Duncan Sands baldrick at free.fr
Sat Mar 27 10:37:35 PDT 2010


Hi Bill,

> A clean-up needs to be marked as a clean-up. It could cause problems in the
> libunwind library otherwise. Take this program for an example:
>
> int main() {
>      try {
>          throw new std::exception();
>      } catch (std::exception *e) {
>          throw e;
>      }
> }
>
> At the re-throw, the libunwind expects the state to be in a clean-up
> state. However, because we generate catch-alls in our code, the personality
> returns the wrong state the libunwind, which then barfs.
>
> Marking that re-throw as a clean-up puts libunwind into the correct state.

my analysis of this problem is that it has nothing to do with libunwind,
in fact it comes from the fact that libstdc++ "knows" that cleanups will
not be run if there are only cleanups when unwinding an exception.

I can reproduce the problem with this testcase both using libunwind and
libgcc.  If compiled with g++ it gives:

terminate called after throwing an instance of 'std::exception*'

If compiled with llvm-g++ it gives:

terminate called without an active exception

I think the reason is that in the g++ case __cxa_end_catch is not called,
i.e. the cleanup that calls __cxa_end_catch is not run, while llvm-gcc
does run the cleanup.

This happens for the following reason: when the second throw is reached,
the unwinder looks up the stack.  In the g++ case, it sees only cleanups,
and terminates the program without running the cleanups.  In the llvm-g++
case, it sees a real handler, a catch-all, so unwinds to it and runs the
cleanup.

This is a pretty interesting example, because there will always be this
difference if invoke follows LLVM semantics, i.e. always causes the code
in the landing pad to be run, since this means that cleanups will always
be run.

Anyway, AFAIK the C++ standard does not specify whether cleanups should
be run or not in this case.  However it looks like the g++ standard
library knows that g++ will generate code that causes the cleanups not
to be run in this case, and exploits it: in __cxa_begin_catch, it pushes
the exception onto a global list.  In the call to terminate, it looks to
see what the last exception pushed onto the list was, and reports it as
the terminating exception (or says "no active exception" if none).  In
__cxa_end_catch the exception is popped off the list.  This scheme for
reporting the right exception in the call to terminate will clearly only
work if __cxa_end_catch has not been called - which is only the case if
cleanups are not run when the program is to be terminated due to there
only being cleanups up the call stack.

> There is other code here, though. If we mark clean-ups as clean-ups, then it
> could break when inlining through an 'invoke' instruction. You will get a
> situation like this:
>
> bb:
>    %ehptr = eh.exception()
>    %sel = eh.selector(%ehptr, @per, 0);
>
> ...
>
> bb2:
>    invoke _Unwind_Resume_or_Rethrow(%ehptr) %normal unwind to %lpad
>
> lpad:
>    ...
>
> The unwinder will see the %sel call as a clean-up and, if it doesn't have a
> catch further up the call stack, it will skip running it. But there *is* another
> catch up the stack -- the catch for the %lpad. However, we can't see that. This
> is fixed in code-gen, where we detect this situation, and convert the "clean-up"
> selector call into a "catch-all" selector call. This gives us the correct
> semantics.

My take on this patch is that it is a horrible hack.  But I don't have a better
solution for the moment.

Ciao,

Duncan.



More information about the llvm-commits mailing list