This is the mail archive of the
mailing list for the GCC project.
Re: robustness vs. conservative GC
- To: "'gcc at gcc dot gnu dot org'" <gcc at gcc dot gnu dot org>
- Subject: Re: robustness vs. conservative GC
- From: "Boehm, Hans" <hans_boehm at hp dot com>
- Date: Fri, 10 Aug 2001 10:14:35 -0700
Tom Lord wrote:
> > If you have specific experience [compilers messing up
> > conservative GC] in deployed systems,
> > I'd be interested in hearing about that.
> Bug fixes have been added to Systas Scheme, and if I recall correctly,
> Guile and SCM Scheme, to fix instantces of this problem caused when
> compiling with GCC, without any optimization flags.
> I don't think there's anything special about those interpreters:
> what would prevent similar bugs from biting GCC or various Java
The fact that the Java collector always treats interior pointers from the
stack as valid, and the other collectors don't. Thus any valid C pointers
stored in the stack or in registers are treated as valid by the collector.
I know of no cases in which a compiler generates unsafe code with this model
and optimization disabled. (Getting a debugger to show you all live
variables at an arbitrary execution point would be really hard if a compiler
did this.) Last I looked at it (many years ago), the Guile scheme was much
> 1c. If the natural way to write a program is to have the program take
> some action when or after a finalizer is executed, that program
> won't always run correctly. I think the Java standard permits
> implementations of this sort and discourages reliance on
> finalizers but that doesn't make such implementations desirable.
You have to be really careful here. There are two kinds of actions you can
take when an object goes away:
1) Synchronous ones, as in C++ destructors. Here it is the programmers
responsibility to know exactly when these are executed, so that they don't
interfere with other parts of the code.
2) Asynchronous as in Java finalizers. Here the programmer has decided to
abdicate responsibility for timing, e.g. because it's hard to predict when
precisely the last pointer to the object will be dropped. These MUST BE
asynchronous. It is incorrect to execute them precisely when the last
pointer is dropped, in the thread that dropped the pointer. They must be
able to acquire locks properly. Thus you can't really use these for
anything other than resource management, wehere the resource is at least
somewhat plentiful. You inherently have no guarantees about when a resource
will be reclaimed, thus having one or two extras in use must be acceptable.
> 2. In the face of an aggressively optimizing C compiler, one that
> sometimes disguises pointers, conservative GC can fail to protect
> some live objects, resulting in program failures. This is a
> famous limitation of conservative GC.
I would argue that that's an implementation bug. You do need that much
compiler support (with the appropriate compiler flag, anyway).
The good news is that with a collector like ours, it's very unlikely to show
up, giving you some time to fix it. I think it's much easier to deal with
than adding precise pointer location information for the stack, for several
1) Different implementors don't need to agree on a descriptor format.
2) You can do it correctly for C, including C unions. Thus mixed language
applications continue to work.
3) You don't instantly break all previously compiled libraries.
(But none of this is an all-or-nothing proposition. If the collector
supports some ambiguous pointers, you can always start to generate type
information for languages for which it's easily feasible, and only treat the
other frames conservatively.)
> Fergus Henderson made the claim that an exact GC implemented without
> compiler support is likely to perform worse than one implemented with
> compiler support. I don't know what "likely" means in that claim, but
> I don't see any a priori reason to believe there will necessarily be
> performance problems. Does anyone else?
For one, you basically can no longer use callee-save registers or stacked
registers for pointers across procedure calls. You need to spill everything
to memory across calls and safe points. And that penalizes even code that
doesn't allocate much. Also nearly every function would need to have
additional code to set up a vtable pointer. (Actually I'd probably make it
some other kind of compile-time constant mark descriptor, but you still need
to reserve space for it and store it.)
My intuition is that this is impractical, especially for something like
Itanium or SPARC. (On Itanium, most functions don't seem to end up with a
regular memory stack frame. This would usually give them one.)