[Gc] workaround for C++ exceptions problem

Filip Pizlo pizlo at mac.com
Wed Jan 25 21:36:40 PST 2006


On Jan 26, 2006, at 12:11 AM, skaller wrote:

>> If you look at my proposed GC_THROW() routine, then you also know.
>> Here it is again:
>>
>> template< typename T >
>> void GC_THROW(const T &exn) {
>>     try {
>>        throw exn;
>>     } catch (const T &exn) {
>>        void *ptr=&exn;
>>        // ptr is the location of the exception slot
>>        throw;
>>     }
>> }
>
> I don't see how this shows how the stack is maintained.
> This does show how to find the top exception on the stack.

If every exception on the stack was thrown with GC_THROW(), then you  
know where all of the exceptions on the stack are.

>
> Note that your technique has the advantage that there
> is no need to know how the stack is maintained ..
> provided your assumptions are correct :)

As far as I can tell, the GC_THROW() technique only makes one  
assumption: "after an exception is stuffed into its slot, it stays  
there until it is destroyed."  This is as safe an assumption as any.   
In particular, it is no more liberal of an assumption than the one  
made in my override technique.  There, I must assume that  
__cxa_allocate_exception() is the only function used by the runtime  
to allocate memory for exception slots.  Of course, the runtime  
doesn't have to be restricted in this way. :-)

>
> Oh .. BTW .. there is a BUG in your code: it isn't
> const correct, you cannot store a T const* into
> a void*. You need to cast away the const for this
> to be conforming. (I think the rule is stupid
> and tried to convince Stroustrupp he didn't know
> what he was doing ..:)

Oh, well.  const or no const, you obviously figured out what I was  
after. :-)

>>> Both implementations are viable. In both cases
>>> the GC MIGHT be able to keep track of everything
>>> just knowing the top slot, assuming it is in a fixed
>>> place (which is stupid since it isn't thread safe unless
>>> that place is in TLS)
>>
>> I tried to determine from the C++ spec if there are some constraints
>> here.  Couldn't figure it out.
>
> I'm not surprised :) C++ doesn't standardise dynamic loading
> or thread handling. Both have to work right together with
> exceptions.
>
>> My guess is that the linked list approach is so much more efficient
>> that nobody is going to do it any other way.
>
> The machine stack is probably faster due to cache coherence
> issues BUT it is harder to organise, and really, who cares
> about blinding speed whilst stack unwinding?

How would the machine stack be faster?  When you throw the exception,  
you can't put it on the top of the stack, because the top of the  
stack will be blown away and reused by destructors.  So where would  
you put the exception?

I don't see any reasonable way of using the machine stack for  
exceptions.

>
>> I thought the
>> point was to make a copy of the exception so that you know what
>> pointers it has.  Or is this saying the same thing?
>
> I don't see how it is possible to copy the exception,
> except at the point it is thrown?

Right, that's exactly what you do.  Users have to throw exceptions  
using the GC_THROW function.  Every use of:

throw e;

must be replaced with:

GC_THROW(e);

>
> Also minor problem .. if the object contains pointers
> into itself .. a bitwise copy will not preserve that.

Yeah, but why would you care about those pointers?  They would point  
at an object that the GC doesn't know about, so the GC will ignore  
them, which is exactly the right thing to do.

>
> If the object has virtual bases such pointers always
> exist or can be constructed, even if the user didn't create them.
> If they're offsets, you'll get a different result to full
> pointers .. the pointer will point to the real exception.
>
> This is messy .. :)

But why would those pointers cause trouble?

>
> [Hummm .. and Felix GC has a way to move objects .. didn't
> think about those virtual base pointers .. :[
>
>> Perhaps it is because I don't see Hans' proposal this way, but I
>> think that with my hacks, Hans' proposal is less brittle than mine.
>> It doesn't require knowing what the ABI is, only that once an
>> exception is dropped into its slot, it stays there.  Of course, there
>> is actually no guarantee that this will be the case, but both
>> proposals are equally flawed in this regard.
>
> I was assuming that Hans was assuming there was a single fixed
> 'slot' which I was trying to point out is not correct.

Right, but that is not a big problem.  Look at my reply to Hans.   
I've pretty much convinced myself that the following will work:

1) Replace all uses of 'throw' with GC_THROW(), where GC_THROW() does  
something like:

template< typename T >
void GC_THROW(const T &exn) {
    try {
       throw exn;
    } catch (T &exn) {
       GC_add_exception_roots(&exn,&exn+1);
       throw;
    }
}

2) Make sure that all exceptions call the following method in their  
destructor:

MyException::~MyException() {
    GC_remove_exception_roots_whose_range_includes(this);
}

This can be simplified by providing a baseclass like gc_exception  
that does this for you.

Where GC_add_exception_roots() and  
GC_remove_exception_roots_whose_range_includes() are two new  
functions.  They could even be based on the existing root adding and  
removing functionality.

>
> i would have guessed that there is indeed a single fixed 'top of
> the exception stack' slot, and the remaining exceptions
> are on a linked list.
>
> In that case, the top slot will definitely change as
> the stack is pushed and popped.
>
> however this is too confusing to talk about.. the actual
> code in front of you has the answer :)
>
>> This is a non-stop issue for anyone using C++ in GCC.
>
> Yeah .. no wonder most language implementors target
> assembler or C. Am I the only one targetting C++?

We've got a VM called Ovm that converts bytecode to C++.  We're quite  
happy doing that.  I've got a whole bunch of code that uses C++ that  
helps run a telescope array (over 130kloc by now).  It works nicely.   
When it comes to production code, I pretty much use C++ for  
everything these days, and have no plans of moving to any other  
language.

>
>> But I've been
>> following the basic exception throwing machinery for quite some time,
>> and it always seems to have had the same basic elements.  So I feel
>> quite comfortable overriding __cxa_allocate_exception/
>> __cxa_free_exception.
>
> In that case can you shed some light on this (off list if you like):
>
>>> -------
>>> The current gcc implementation of catch sucks, it doesn't
>>> work properly across DLL boundaries -- so the ABI may
>>> well change again  :)
>
> because it is a pretty serious pain for me.

I've seen similar things with C++ dylibs on Mac OS X.  Haven't  
investigated it too much.  I would guess it has something to do with  
the linker's handling of exception handler tables, which is something  
I've never bothered to look at.



More information about the Gc mailing list