This is the mail archive of the
libstdc++@gcc.gnu.org
mailing list for the libstdc++ project.
Re: Implementing Exception Propagation (N2179)
- From: Sebastian Redl <sebastian dot redl at getdesigned dot at>
- To: libstdc++ at gcc dot gnu dot org
- Date: Mon, 19 May 2008 16:47:00 +0200
- Subject: Re: Implementing Exception Propagation (N2179)
- References: <482F4610.3020008@getdesigned.at>
Some more thoughts on this.
Sebastian Redl wrote:
To implement the proposal, I believe that these changes are necessary:
To libsupc++:
1) Extend struct __cxa_exception with two fields:
Another field is necessary, storing the size of the exception object, as
passed to __cxa_allocate_exception originally. This information is not
stored anywhere else, so no copy can be made without it.
3) Implement the three functions of N2179:
3b) rethrow_exception() calls __cxa_allocate_exception() to create
space for a new exception object. It copies the relevant state and
copies the exception object using the stored copy constructor. It then
calls _Unwind_RaiseException.
Here's an interesting tidbit. The standard requires that exceptions use
the unexpected_handler and terminate_handler set when the exception was
thrown. In other words, if a destructor called during cleanup calls
set_unexpected_handler() or set_terminate_handler(), this should have no
effect on the exception. For this reason, these handlers are stored by
__cxa_throw in the exception object.
With the current __cxa_rethrow implementation, an exception rethrown by
a simple throw does not change these handlers. In other words, this does
not affect the exception:
catch(...) {
set_unexpected_handler(myhandler);
throw;
}
However, from my understanding of the description of std::terminate(),
this is actually a bug in libstdc++, not the intent of the standard:
[18.7.3.3]/2
"Calls the terminate_handler function in effect immediately after
evaluating the throw-expression, if called by the implementation, [...]"
What about rethrow_exception(), then? Should it copy the terminate and
unexpected handlers from the old exception, and thus act like the
current implementation of rethrow? Or should it acquire the current
handlers anew and thus pretend it contains a throw-expression?
If, and only if, the passed exception_ptr is the only reference to the
exception (handlerCount == 0 and the reference count == 1),
rethrow_exception() may simply throw the existing exception object.
I've realized that this is problematic. rethrow_exception() takes its
argument by value. Thus, during the call, there will be at least two
exception_ptrs to this exception: the argument, and the pointer passed
as the argument. That is, unless exception_ptr has a move constructor,
in which case the outer pointer could be moved to the argument, making
it just one pointer.
This means that the optimization is not possible, unless one of two
things are done:
1) Make the argument a const reference. I'm not sure if this change in
signature is allowed by the standard.
2) Don't ever give exception_ptr move semantics. Given that copying it
should be quite fast, this ought to be fine.
3c) While the simple implementation of copy_exception works, it's
inefficient. It would be more efficient to call
__cxa_allocate_exception() and initialize the resulting object, then
return an exeption_ptr referring to it. However, this requires
compiler support, as there is no library way of obtaining the
destructor and copy constructor of a type.
Is there any precedent for the compiler generating low-level
instantiations of a template?
To the C++ frontend itself:
4) Pass the copy constructor to __cxa_throw.
Therefore, it may be necessary to construct a stub that fits the
signature and calls the copy constructor with default arguments as
required, then pass that stub to __cxa_throw.
For example, a valid stub implementation would be
template <typename E>
void copy_constructor(E *dest, const E *src)
{
new (dest) E(*src);
}
I have no idea how I would inject this definition into GCC's tree,
though - especially how to avoid dupes.
As such, the exception class would have to be changed. This would make
interoperation with old code at least partially possible - the
exceptions would be relegated to the status of foreign exceptions,
could be caught with catch(...) only, and could not be nested.
Possible classes are "GNU2C++\0" or "2NUCC++\0".
Actually, since we're breaking the common C++ ABI (extending the
__cxa_throw prototype and changing __cxa_exception), we should change no
the vendor part ("GNUC") but the language part "C++\0", perhaps to
"C+1\0" or "C+09".
By the way, I got impatient and implemented this, including the changes
to G++. All I need now is the whole copy constructor thing, then I can
compile and test.
(I'd need test cases, too. So I've still got things to write. Lots of
things - it's a simple proposal, but with very complex applications.)
Still hoping for answers,
Sebastian