clang/g++ frontend: can __cxa_end_catch throw?

When I compile a piece of C++ with exceptions into .ll I see that gcc frontend in some cases calls __cxa_end_catch with 'call' and in other cases with 'invoke' with termination in case of exception. clang++ always just calls __cxa_end_catch with 'call' instruction.

Which way is correct?

--- c.cpp ---
#include <stdio.h>
#include <stdlib.h>

struct C {
   C();
   ~C();
};

struct E {
   E();
   ~E();
};

void mythrowing1();
void mythrowing2();
void mycatching(E *e);

void myfunc() {
   try {
   C c;
     mythrowing1();
     mythrowing2();
   } catch (E *e) {
     mycatching(e);
   }
}

--- commands ---
/usr/local/llvm/2.7/bin/c++ -O3 -fexceptions -emit-llvm -S -o c-gcc.ll c.cpp
/usr/local/llvm/2.7/bin/clang++ -O3 -fexceptions -emit-llvm -S -o c-clang.ll c.cpp

Hi Yuri,

When I compile a piece of C++ with exceptions into .ll I see that gcc
frontend in some cases calls __cxa_end_catch with 'call' and in other
cases with 'invoke' with termination in case of exception. clang++
always just calls __cxa_end_catch with 'call' instruction.

IIRC, __cxa_end_catch may throw an exception because it runs the destructor
for the exception object, which can execute arbitrary user code and thus may
throw an exception. This is why it is sometimes correct to use invoke for it.
However in the case of your example it seems that llvm-gcc didn't optimize the
code as well as it might, since the invoke is redundant.

Ciao,

Duncan.

But there are cases when the outcomes will be different. In case of gcc frontend terminate() will be called when the original exception's destructor throws an exception and in case of clang Unwind_Resume_or_Rethrow will be called which is different.

I think one of these cases must be an incorrect behavior.

Yuri

Hi Yuri,

But there are cases when the outcomes will be different. In case of gcc
frontend terminate() will be called when the original exception's
destructor throws an exception and in case of clang
Unwind_Resume_or_Rethrow will be called which is different.

since Unwind_Resume_or_Rethrow causes the exception to continue to unwind,
the result is also a call to terminate().

Ciao,

Duncan.

For your test case, clang++'s code is correct (and better) because
__cxa_end_catch won't throw for a caught exception of that type. That said,
clang++'s code is incorrect in general, because it actually never emits an invoke for
__cxa_end_catch (and actually marks it nounwind); I've been assuming a model
where (1) exceptions from destroying an exception temporary triggered a terminate
instead of a normal unwind, which they don't, and (2) the ABI took care of that possibility
for us, which it doesn't, because it can't.

clang++ is also broken in this respect because an exception thrown from
__cxa_end_catch along the unwind path does need to result in a call to std::terminate.

John.

Which type? There are two pending exceptions at the moment when __cxa_end_catch is called, both with invisible destructors: E* and unknown type thrown from mycatching(). __cxa_end_catch attempts to delete E* and proceed to rethrow the unknown one when the other unknown exception is thrown by E::~E(). Isn't this the condition for terminate()?

Yuri

I think you're confused about the semantics of C++ EH. In C++, the only time you have to call a destructor for an exception is when the exception is of class type. An exception that's caught by A* is never of class type; by definition, it's of *pointer* to class type (specifically, either A or some class derived from A; we can't make assumptions about its destructor from properties of A's destructor). There is no implicit delete of the pointed-to object.

In theory, a program could call _cxa_throw itself, providing an exception of pointer type together with a special "destructor" function which deletes the pointer, but AFAIK that's an accident of the ABI API, not a supported operation; we're allowed to optimize based on an assumption that an exception of pointer type never has a destructor.

If mycatching throws an exception, then we do need to call _cxa_end_catch on the way out of the catch block for the E*. That's done in a landing pad for the invoke, but that landing pad never technically catches this exception, which is to say, that landing pad never calls __cxa_begin_catch. Therefore we shouldn't (and don't) call __cxa_end_catch, and therefore the destructor for that object is not run.

John.