Re: GC safety and return values
Tom Lord 26 Dec 2003 18:14 UTC
> From: Michael Sperber <xxxxxx@informatik.uni-tuebingen.de>
> Jim> But the thread-related problems with GCPRO that I don't see how
> Jim> to solve are those created by the user's compiler rearranging
> Jim> code that operates directly on heap references. The compiler is
> Jim> free to make copies of heap references in registers where a
> Jim> copying GC can't find them to update them.
> I don't think so---the typical GCPRO annotation (such as that in
> Scheme 48) is going to take the address of the reference (which the
> SRFI allows) which limits the compiler's ability to do such things
> exactly in the way desired.
> (This isn't a comment on the general issue, just on this specific
> point.)
jimb is correct.
Consider the code:
scheme_value x;
[...]
GCPRO(&x);
[...]
z = SCHEME_CONS (x, y); /* XXX */
during execution of the the statement marked "XXX", the sequence
over time of operations may be:
thread 1: thread 2:
|
reg1 = x; - t
reg2 = y; - i
- trigger GC m
call SCHEME_CONS - e
(on return, - |
reg1 holds new pair) - V
- trigger GC
z = reg1 -
At the first GC, a stop-and-copy GC will want to modify the values
stored in x and y, but it won't find reg1 and reg2.
At the second GC, a stop-and-copy GC will want to modify the value
about to be stored in z, but it won't find reg1.
That is one reason why it isn't sufficient to make sure that protected
scheme values are always stored in GCPROtected locations -- you must
also make sure that, other than in the internals of the FFI
implementation, they are not stored anywhere else.
You can make that guarantee by not passing or returning those values
directly at all -- but instead passing and returning "handles" for
those values.
Jimb and I have each shown a technique (so now you have two available)
for always passing and returning handles: in jimb's approach, handles
are separately allocated objects; in my approach, handles are the
addresses of GCPROtected values. So you have either:
scheme_value_handle x = 0;
scheme_value_handle y = 0;
scheme_value_handle z = 0;
[....]
z = SCHEME_CONS (this_call, x, y);
/* caller frees any handles allocated above. */
or:
struct my_frame
{
scheme_value x;
scheme_value y;
scheme_value z;
} f;
GCPRO_FRAME (f);
[...]
SCHEME_CONS (&f.z, this_instance, &f.x, &f.y);
/* eventual GCUNPRO_FRAME needed */
(As I said elsewhere, I think that there are some performance and,
more importantly, GC-precision advantages to the second approach.)
Either approach also has advantages for single-threaded systems simply
because they both make it harder to make certain kinds of mistakes
that will lead to subtle and difficult-to-reproduce GC bugs.
-t