[Gc] Cheaper finalisation and weak pointers

Petter Urkedal petter.urkedal at nordita.dk
Thu Feb 10 14:55:55 PST 2005


First, I am sorry that two essential files, weakptr.c and fiq.c, were
missing from the previous patch, and unfortunately also the changes to
Makefile.am that would have exposed the error when I testing it.  I
attach a new patch.  (Run autoconf/automake.)

On 2005-02-09, Boehm, Hans wrote:
> There seem to be a few applications like yours where performance
> of a finalization-like mechanism matters.
> 
> A finalization plug-in interface generally sounds like a good idea,
> since I
> suspect there are many slightly different useful variants.

Yes, the finalisation queue is only a semi-general solution, since it
makes the assumptions that it needs for the urgent inspection work, but
then queues the rest to be done by client code in an unrestricted
environment.  (For instance, I can imagine someone could get rid of the
pointer in front of the object pointer that the finalisation queue
needs.  Cf. [1].)

The more general approach, I think, is to let "client code" do
mark-inspection itself; well, common client code should maybe not do
that, but it would be a convenient way to test extensions outside the GC
source tree and distribute/install them separately until they pass
some stability and "popularity" testing.

I think what is needed is a hook for mark-inspection, and a hook for running
finalisation code, as my previous patch did.  The latter is no problem.  The
former, however, also implies that some low-level interfaces must be
exported, like GC_is_marked and GC_MARK_FO.  It's maybe not an issue if
provided with proper warnings.  So,

1. Shall I prepare a new patch with finalisation queues and weak
pointers stripped out?  If so,

2. shall I selectively move the required low-level stuff from
private/*, or is it better to install the private/* headers under
$(includedir)/gc/private and let implementors of extensions figure
it out?

3. Is it good to split the plug-in registration into two functions, one for
inspection and one for finalisation?

4. I realise the word "plugin" may needlessly challenge casual users (think
Firefox, etc).  It may be better to say (gc/gc_extension.h)

    GC_inspection_extension_handle
    GC_extend_inspection(GC_inspection_extension_proc f, void *cd);

    GC_finalization_extension_handle
    GC_extend_finalization(GC_finalization_extension_proc f, void *cd);

    GC_drop_inspection(GC_inspection_extension_handle);

    GC_drop_finalization(GC_finalization_extension_handle);

or similar.

If this is the solution, the rest of this e-mail is less important, but I
will answer your questions anyway.  I also speculate on possible
parallelisation of the inspection procedure at the end.


> I'd like to understand a bit better where exactly the trade-offs
> are here.
> 
> >     The solutions using the present GC functionality gives the
> >     following overhead, not counting asymptotically negligible parts:
> > 
> > 	disappearing links:		5 words extra, 3 fragments
> > 	    hash-table links must be moved into separate memory
> > 	    fragments (+ 1 word) and disappearing links cost 4 words.
> Could you explain why hash-table links have to be moved to separate
> memory?

Given for instance some hashed pair constructor (ignoring details about
polymorphism), the most compact implementation would be to put the hash
table link directly into the objects:

    struct pair {
	GC_hidden_pointer hash_next;
	struct pair *left, *right;
    };

To take notice of the disappearance of a '*pair' object, I need to call
'GC_general_register_disappearing_link(link, pair)', but on which 'link'?
If 'link' is the '&hash_next' of the previous object, then the following
objects will also be lost from the hash table.  I could let the collector
clear 'left' or 'right' just as a message to client code, but then the
object might be reclaimed before client code takes notice and unlinks it.
Thus, my conclusion is that a disappearing link solution does not allow
hash links to be inlined into the hash-constructed objects, and I have

    struct hash_node {
	struct hash_node *next;
	GC_hidden_pointer obj; /* disappearing link */
    };
    struct pair {
	struct pair *left, *right;
    };

for each pair (in addition to amortised 2 words for a half-full hash table).
The 'obj' link is the extra link, and it applies also to my proposed
weak pointers vs finalisation queues.  An extra 1 word is not a big deal,
but the extra memory fragment per object, I think I care about,
especially if it is of a different size so that it is likely to be
allocated in a different page of memory.

> (I may misunderstand what that means.  I count 4 words in the GC's
> data structures, with one of those in the hash table array.)
> 
> It seems to me that you potentially win for a couple of different,
> separable, reasons:
> 
> 1) You avoid the GC's internal hash table, since you don't allow
> pointers
> to be unregistered.
> 
> 2) Since the linked list is in the object itself, you don't need a
> pointer to the object being tracked.
> 
> Does that sound right?

Precisely.  Also, I think 1) is conceptually sound if we consider the
weak pointer property of the link to be type information, and thus
constant throughout the object's life time.  Re-registration is not
needed when the pointer is assigned.

> Have you thought about the concurrency issues here?  Normally you need
> to
> lock on a "disappearing" pointer dereference, to deal with the case of
> the GC
> clearing the reference while you only have the hidden pointer in a
> register.

My GC_weakptr_get was incorrect, thanks!  Now, I could do locking, but
consider adding a volatile to the struct

    typedef struct {
	GC_word next;
	volatile GC_word hptr;
    } GC_weakptr;

and implementing GC_weakptr_get as

    GC_API void *GC_weakptr_get(GC_weakptr *wptr)
    {
	void * volatile ptr = REVEAL_POINTER(wptr->hptr);
	if (wptr->hptr == HIDE_POINTER(0))
	    return 0;
	else
	    return ptr;
    }

I think this should force the compiler to store the revealed pointer to
the 'ptr' stack variable before re-fetching 'wptr->hptr'.  The C
standard may not be very specific on the volatile; my assumption is that
the C compiler can not commute operations on volatile locations.  At
least gcc produces correct code with '-O9' (".loc" and labels removed):

GC_weakptr_get:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $4, %esp
        movl    8(%ebp), %edx       %edx = wptr
        movl    4(%edx), %ecx
        notl    %ecx
        movl    %ecx, -4(%ebp)      Store revealed pointer to ptr...
        movl    4(%edx), %ecx       ...before re-fetch from wptr->hptr
        movl    -4(%ebp), %eax
        incl    %ecx
        sete    %dl
        movl    %ebp, %esp
        popl    %ebp
        movzbl  %dl, %ecx
        decl    %ecx
        andl    %ecx, %eax          (Heh, nice trick to avoid jump!)
        ret

If we are uncertain, I can add an option in 'configure.in' to
--disable-volatile-assumptions and LOCK() instead.

(BTW, I think neither _WeakPointer_Equal nor _WeakPointer_Hash in
gcc_support.c need to call with allocation lock, as they don't
dereference or return the object pointers.)

> What locking discipline do you need?
 
My current code assumes that word-size stores are atomic.  The weak
pointers utilises the separation of data between mark-inspection code
and client code as much as possible, and uses atomic operations.  The
finalisation queues uses LOCK() and UNLOCK(), which can be rewritten
as GC_call_with_alloc_lock if moved out of the GC tree.  I think I
could change it to use atomic operations only; I had that in mind
when I wrote it.  However, see [1] for an approach that would need
locking.

If mark-inspectors have to run with application threads stopped, then
using GC's lock seems to be the easiest way to avoid dead-locking.  Else
the stopped threads may hold locks on containers which are needed by the
inspector.  More granular locking could be implemented, if (1) the
client could register additional mutices that GC must claim before a
full GC, or (2) if GC can start threads but still hold allocation lock
before starting mark-inspectors.  In both cases, extensions must make
sure not to allocate memory while holding a lock required by the
inspection code.  Is that analysis sound?

The mark-inspector codes can not run parallel as they are now, but I
think that would be nice.  If it is possible from GC's point of view to
run mark-inspectors in parallel, it would also be nice to have
inter-inspector mutices working.  This is not strictly necessary since
the client could set up several inspectors to work on disjoint sets of
objects.  Parallelisation would not prevent delays for code that uses
heavy inspection, but at least it would utilise all CPUs (in the
upcoming cheap multi-core machines :-))

Continuing on the ambitious side, can applications run concurrently with
mark-inspection as long as there are thread-local free-lists available?
Then, given a proper scaling of local alloc pools, even delays could be
eliminated.  I don't mind GC delays in my own code, and maybe apps which
need some real-time behaviour better be advised not to use
mark-inspection-intensive techniques.

Sincerely,
Petter


A FOOTNOTE

[1]  My first attempt at finalisation had no extra pointer to queue the
finalisable objects.  Instead, the client provided a function to iterate
over a container and unlink unmarked nodes directly.  Thus the GC memory
overhead per node approached zero for large containers.  However, I
could not find a good solution for locking the container between
application-phase and the GC post-mark phase without risking deadlocks.
My solution was to separate data needed by the two worlds, thus
finalisation queueing.  I realise that I could probably have used
GC_call_with_alloc_lock in client code, but finalisation queueing also
has the advantage of minimising work during GC.
-------------- next part --------------
diff -Naur gc6.4/configure.in gc6.4-fiq/configure.in
--- gc6.4/configure.in	2004-12-18 02:28:48.000000000 +0100
+++ gc6.4-fiq/configure.in	2005-02-01 19:35:00.000000000 +0100
@@ -450,7 +450,7 @@
 
 AM_CONDITIONAL(USE_LIBDIR, test -z "$with_cross_host")
 
-AC_OUTPUT([Makefile doc/Makefile include/Makefile],,
+AC_OUTPUT([Makefile doc/Makefile include/Makefile contrib/Makefile],,
 srcdir=${srcdir}
 host=${host}
 CONFIG_SHELL=${CONFIG_SHELL-/bin/sh}
diff -Naur gc6.4/include/Makefile.am gc6.4-fiq/include/Makefile.am
--- gc6.4/include/Makefile.am	2003-05-30 02:11:51.000000000 +0200
+++ gc6.4-fiq/include/Makefile.am	2005-02-02 18:33:25.000000000 +0100
@@ -16,12 +16,13 @@
 # installed headers
 #
 pkginclude_HEADERS = gc.h gc_typed.h gc_inl.h \
-    gc_inline.h gc_mark.h gc_cpp.h \
+    gc_inline.h gc_mark.h gc_cpp.h gc_plugin.h \
     weakpointer.h gc_alloc.h new_gc_alloc.h \
     gc_allocator.h gc_backptr.h \
     gc_gcj.h gc_local_alloc.h leak_detector.h \
     gc_amiga_redirects.h gc_pthread_redirects.h \
-    gc_config_macros.h
+    gc_config_macros.h \
+    gc_plugin.h gc_weakptr.h gc_fiq.h
 
 # headers which are not installed
 #
diff -Naur gc6.4/include/gc_plugin.h gc6.4-fiq/include/gc_plugin.h
--- gc6.4/include/gc_plugin.h	1970-01-01 01:00:00.000000000 +0100
+++ gc6.4-fiq/include/gc_plugin.h	2005-01-31 18:42:00.000000000 +0100
@@ -0,0 +1,29 @@
+#ifndef _GC_PLUGIN_H
+
+# define _GC_PLUGIN_H
+
+#include "gc.h"
+
+# ifdef __cplusplus
+    extern "C" {
+# endif
+
+typedef void (*GC_plugin_inspector_proc) GC_PROTO((void *cd));
+typedef void (*GC_plugin_finalization_proc) GC_PROTO((void *cd));
+typedef struct GC_plugin *GC_plugin_handle;
+
+GC_API GC_plugin_handle GC_register_plugin
+	GC_PROTO((GC_plugin_inspector_proc ip,
+		  GC_plugin_finalization_proc fp,
+		  void *cd));
+GC_API GC_plugin_handle GC_register_plugin_inner
+	GC_PROTO((GC_plugin_inspector_proc ip,
+		  GC_plugin_finalization_proc fp,
+		  void *cd));
+
+GC_API void GC_unregister_plugin GC_PROTO((GC_plugin_handle));
+
+# ifdef __cplusplus
+    }
+# endif
+#endif
diff -Naur gc6.4/include/gc_fiq.h gc6.4-fiq/include/gc_fiq.h
--- gc6.4/include/gc_fiq.h	1970-01-01 01:00:00.000000000 +0100
+++ gc6.4-fiq/include/gc_fiq.h	2005-02-02 20:24:48.000000000 +0100
@@ -0,0 +1,78 @@
+#ifndef _GC_FIQ_H
+# define _GC_FIQ_H
+# include "gc.h"
+# ifdef __cplusplus
+    extern "C" {
+# endif
+
+struct GC_fiq_queue;
+
+typedef void (*GC_fiq_finalizer_proc) GC_PROTO((struct GC_fiq_queue *q));
+	/* This is the type of finalizers which are attached to		*/
+	/* finalization queues.  They are called after each global	*/
+	/* mark, and may be used to process the objects queued for	*/
+	/* finalization.						*/
+
+/* Internal, needed for private fields of GC_fiq_queue.			*/
+typedef char *GCpriv_ptr_t;
+typedef void (*GCpriv_finalization_mark_proc) GC_PROTO((char *fo));
+
+/* The GC_fiq_queue struct holds the data to manage finalization	*/
+/* queues.  It is typically embedded in "weak" containers, i.e.		*/
+/* containers with nodes which may be collected before the container	*/
+/* itself.  This can be done by hiding pointers to nodes from the	*/
+/* collector, and unlinking objects which appear in the queue.		*/
+struct GC_fiq_queue
+{
+    /* OBS. Fields of this struct are private and should not be		*/
+    /* modified directly by client code.				*/
+    GC_word next_in_gc;
+    GC_word live;
+    GC_word dead;
+    GC_word *dead_end;
+    GC_fiq_finalizer_proc finalize;
+    GCpriv_finalization_mark_proc mark_proc;
+};
+
+GC_API void GC_fiq_queue_init
+	GC_PROTO((struct GC_fiq_queue *q, GC_fiq_finalizer_proc finalize));
+GC_API void GC_fiq_queue_init_ignore_self
+	GC_PROTO((struct GC_fiq_queue *q, GC_fiq_finalizer_proc finalize));
+GC_API void GC_fiq_queue_init_no_order
+	GC_PROTO((struct GC_fiq_queue *q, GC_fiq_finalizer_proc finalize));
+	/* These functions initialize a finalization queue and		*/
+	/* associates an optional finalizer to be run after each global	*/
+	/* mark.  Objects which are allocated with GC_fiq_malloc or	*/
+	/* GC_fiq_malloc_atomic will appear in q when they become	*/
+	/* unreachable.							*/
+	/* The _ignore_self and _no_order variants are analogous to	*/
+	/* the corresponding GC_register_finalizer variants.		*/
+
+GC_API void *GC_fiq_malloc GC_PROTO((struct GC_fiq_queue *q, size_t size));
+	/* Allocate non-atomic memory which will appear in q when	*/
+	/* determined to be unreachable by the collector.		*/
+
+GC_API void *GC_fiq_malloc_atomic GC_PROTO((struct GC_fiq_queue *q,
+					    size_t size));
+	/* Allocate atomic memory which will appear in q when		*/
+	/* determined to be unreachable by the collector.		*/
+
+#define GC_fiq_queue_is_empty(q) ((q)->dead == 0)
+	/* This function returns non-zero iff q has no objects ready	*/
+	/* for finalization.						*/
+
+GC_API void *GC_fiq_queue_pop GC_PROTO((struct GC_fiq_queue *q));
+	/* Pop off the next object in q to be finalized.  If there are	*/
+	/* no objects left in q, return NULL.				*/
+
+#define GC_fiq_obj_is_dead(ptr) ((*((GC_word *)(ptr) - 1) & 1) == 0)
+	/* This provides a quick test if the object at ptr, which must	*/
+	/* have been allocated with GC_fiq_malloc or			*/
+	/* GC_fiq_malloc_atomic, has been put in the corresponding	*/
+	/* finalization queue.  Call this function before using a node	*/
+	/* in a weak container, to make sure it is still valid.		*/
+
+# ifdef __cplusplus
+    }
+# endif
+#endif
diff -Naur gc6.4/include/gc_weakptr.h gc6.4-fiq/include/gc_weakptr.h
--- gc6.4/include/gc_weakptr.h	1970-01-01 01:00:00.000000000 +0100
+++ gc6.4-fiq/include/gc_weakptr.h	2005-02-10 19:08:30.000000000 +0100
@@ -0,0 +1,14 @@
+#ifndef _GC_WEAKPTR_H
+# define _GC_WEAKPTR_H
+# include "gc.h"
+
+typedef struct {
+    GC_word next;
+    volatile GC_word hptr;
+} GC_weakptr;
+
+GC_API void GC_weakptr_init GC_PROTO((GC_weakptr *wptr, void *ptr));
+GC_API void *GC_weakptr_get GC_PROTO((GC_weakptr *wptr));
+#define GC_weakptr_set(wptr, ptr) ((wptr)->hptr = ~(GC_word)(ptr))
+
+#endif
diff -Naur gc6.4/Makefile.am gc6.4-fiq/Makefile.am
--- gc6.4/Makefile.am	2004-12-17 01:27:52.000000000 +0100
+++ gc6.4-fiq/Makefile.am	2005-02-01 19:34:41.000000000 +0100
@@ -20,7 +20,7 @@
 
 AUTOMAKE_OPTIONS = foreign
 
-SUBDIRS = doc include
+SUBDIRS = doc include contrib
 
 EXTRA_DIST = 
     ## more items will be succesively added below
@@ -51,7 +51,8 @@
 solaris_pthreads.c solaris_threads.c specific.c stubborn.c typd_mlc.c \
 backgraph.c win32_threads.c \
 pthread_support.c pthread_stop_world.c darwin_stop_world.c \
-$(asm_libgc_sources)
+$(asm_libgc_sources) \
+fiq.c weakptr.c
 
 # Include THREADDLLIBS here to ensure that the correct versions of
 # linuxthread semaphore functions get linked:
diff -Naur gc6.4/finalize.c gc6.4-fiq/finalize.c
--- gc6.4/finalize.c	2004-08-27 02:35:55.000000000 +0200
+++ gc6.4-fiq/finalize.c	2005-02-02 20:26:53.000000000 +0100
@@ -15,6 +15,7 @@
 /* Boehm, February 1, 1996 1:19 pm PST */
 # define I_HIDE_POINTERS
 # include "private/gc_pmark.h"
+# include "gc_plugin.h"
 
 # ifdef FINALIZE_ON_DEMAND
     int GC_finalize_on_demand = 1;
@@ -85,12 +86,21 @@
 
 word GC_fo_entries = 0;
 
+struct GC_plugin {
+    struct GC_plugin *prev, *next;
+    GC_plugin_inspector_proc inspect;
+    GC_plugin_finalization_proc finalize;
+    void *cd;
+} GC_plugin_head = { &GC_plugin_head, &GC_plugin_head, 0, 0, 0 };
+
 void GC_push_finalizer_structures GC_PROTO((void))
 {
     GC_push_all((ptr_t)(&dl_head), (ptr_t)(&dl_head) + sizeof(word));
     GC_push_all((ptr_t)(&fo_head), (ptr_t)(&fo_head) + sizeof(word));
     GC_push_all((ptr_t)(&GC_finalize_now),
 		(ptr_t)(&GC_finalize_now) + sizeof(word));
+    GC_push_all((ptr_t)(&GC_plugin_head),
+		(ptr_t)(&GC_plugin_head) + sizeof(GC_plugin_head));
 }
 
 /* Double the size of a hash table. *size_ptr is the log of its current	*/
@@ -511,6 +521,61 @@
     				ocd, GC_null_finalize_mark_proc);
 }
 
+GC_API GC_plugin_handle
+	GC_register_plugin_inner(GC_plugin_inspector_proc inspect,
+				 GC_plugin_finalization_proc finalize,
+				 void *cd)
+{
+    GC_plugin_handle h;
+    h = (GC_plugin_handle)GC_INTERNAL_MALLOC(sizeof(struct GC_plugin), NORMAL);
+    if (!h) {
+	WARN("Insufficient memory for registration of plugin.", 0);
+	return NULL;
+    }
+    h -> inspect = inspect;
+    h -> finalize = finalize;
+    h -> next = &GC_plugin_head;
+    h -> prev = GC_plugin_head.prev;
+    h -> prev -> next = h;
+    GC_plugin_head . prev = h;
+    return h;
+}
+
+GC_API GC_plugin_handle GC_register_plugin(GC_plugin_inspector_proc inspect,
+					   GC_plugin_finalization_proc finalize,
+					   void *cd)
+{
+    GC_plugin_handle h;
+    DCL_LOCK_STATE;
+#   ifdef THREADS
+	LOCK();
+#   endif
+    h = GC_register_plugin_inner(inspect, finalize, cd);
+#   ifdef THERADS
+	UNLOCK();
+#   endif
+    return h;
+}
+
+GC_API void GC_unregister_plugin_inner(GC_plugin_handle h)
+{
+    h -> prev -> next = h -> next;
+    h -> next -> prev = h -> prev;
+    h -> prev = h -> next = NULL;
+}
+
+GC_API void GC_unregsiter_plugin(GC_plugin_handle h)
+{
+    DCL_LOCK_STATE;
+#   ifdef THREADS
+	LOCK();
+#   endif
+    GC_unregister_plugin_inner(h);
+#   ifdef THREADS
+	UNLOCK();
+#   endif
+}
+
 #ifndef NO_DEBUGGING
 void GC_dump_finalization()
 {
@@ -549,6 +614,7 @@
     register int i;
     int dl_size = (log_dl_table_size == -1 ) ? 0 : (1 << log_dl_table_size);
     int fo_size = (log_fo_table_size == -1 ) ? 0 : (1 << log_fo_table_size);
+    GC_plugin_handle curr_plugin;
     
   /* Make disappearing links disappear */
     for (i = 0; i < dl_size; i++) {
@@ -665,6 +731,12 @@
         }
       }
     }
+
+    for (curr_plugin = GC_plugin_head.next;
+	 curr_plugin != &GC_plugin_head;
+	 curr_plugin = curr_plugin->next)
+	if (curr_plugin->inspect)
+	    (*curr_plugin->inspect)(curr_plugin->cd);
 }
 
 #ifndef JAVA_FINALIZATION_NOT_NEEDED
@@ -765,7 +837,14 @@
     int count = 0;
     word mem_freed_before;
     DCL_LOCK_STATE;
-    
+    GC_plugin_handle curr_plugin;
+
+    for (curr_plugin = GC_plugin_head.next;
+	 curr_plugin != &GC_plugin_head; 
+	 curr_plugin = curr_plugin->next)
+	if (curr_plugin->finalize)
+	    (*curr_plugin->finalize)(curr_plugin->cd);
+
     while (GC_finalize_now != 0) {
 #	ifdef THREADS
 	    DISABLE_SIGNALS();
diff -Naur gc6.4/weakptr.c gc6.4-fiq/weakptr.c
--- gc6.4/weakptr.c	1970-01-01 01:00:00.000000000 +0100
+++ gc6.4-fiq/weakptr.c	2005-02-10 19:28:25.000000000 +0100
@@ -0,0 +1,56 @@
+#define I_HIDE_POINTERS
+# include "gc_weakptr.h"
+# include "gc_plugin.h"
+# include "private/gc_priv.h"
+
+static GC_word wptr_link = HIDE_POINTER(0);
+static GC_plugin_handle wptr_plugin = 0;
+
+static void wptr_inspect(void *cd)
+{
+    GC_word *next_ptr = &wptr_link;
+    GC_weakptr *wptr;
+    while ((wptr = REVEAL_POINTER(*next_ptr)) != 0) {
+	void *base = GC_base(wptr);
+	if (base && !GC_is_marked(base))
+	    *next_ptr = wptr->next;
+	else {
+	    void *ptr = REVEAL_POINTER(wptr->hptr);
+	    if (ptr) {
+		void *base = GC_base(ptr);
+		if (!GC_is_marked(base))
+		    wptr->hptr = HIDE_POINTER(0);
+	    }
+	    next_ptr = &wptr->next;
+	}
+    }
+}
+
+static void wptr_finalize(void *cd)
+{
+}
+
+GC_API void *GC_weakptr_get(GC_weakptr *wptr)
+{
+    void * volatile ptr = REVEAL_POINTER(wptr->hptr);
+    if (wptr->hptr == HIDE_POINTER(0))
+	return 0;
+    else
+	return ptr;
+}
+
+GC_API void GC_weakptr_init(GC_weakptr *wptr, void *ptr)
+{
+    DCL_LOCK_STATE;
+#   ifdef THREADS
+	LOCK();
+#   endif
+    if (!wptr_plugin)
+	wptr_plugin = GC_register_plugin_inner(wptr_inspect, wptr_finalize, 0);
+    wptr->hptr = ~(GC_word)ptr;
+    wptr->next = wptr_link;
+    wptr_link = HIDE_POINTER(wptr);
+#   ifdef THREADS
+	UNLOCK();
+#   endif
+}
diff -Naur gc6.4/fiq.c gc6.4-fiq/fiq.c
--- gc6.4/fiq.c	1970-01-01 01:00:00.000000000 +0100
+++ gc6.4-fiq/fiq.c	2005-02-02 20:25:51.000000000 +0100
@@ -0,0 +1,152 @@
+#define I_HIDE_POINTERS
+# include "gc.h"
+# include "private/gc_pmark.h"
+# include "gc_plugin.h"
+# include "gc_fiq.h"
+
+GC_API void GC_normal_finalize_mark_proc(ptr_t);
+GC_API void GC_ignore_self_finalize_mark_proc(ptr_t);
+GC_API void GC_null_finalize_mark_proc(ptr_t);
+
+static GC_word fiq_queue_link = HIDE_POINTER(0);
+static GC_plugin_handle fiq_plugin = 0;
+
+static void fiq_inspect(void *cd)
+{
+    GC_word *link = &fiq_queue_link;
+    struct GC_fiq_queue *q;
+    while ((q = REVEAL_POINTER(*link)) != 0) {
+	void *base = GC_base(q);
+	if (!GC_is_marked(base))
+	    *link = q->next_in_gc;
+	else {
+	    GC_word *p = &q->live;
+	    GC_word *o;
+	    while ((o = REVEAL_POINTER(*p)) != NULL) {
+		if (GC_is_marked((ptr_t)o))
+		    p = o;
+		else {
+		    GC_MARK_FO((ptr_t)o, q->mark_proc);
+		    GC_set_mark_bit((ptr_t)o);
+		    *p = *o;
+		    *o = 0;
+		    *q->dead_end = (GC_word)o;
+		    q->dead_end = o;
+		}
+	    }
+	    link = &q->next_in_gc;
+	}
+    }
+}
+
+static void fiq_finalize(void *cd)
+{
+    struct GC_fiq_queue *q;
+    /* No locking needed assuming word size store is atomic. */
+    for (q = REVEAL_POINTER(fiq_queue_link); q;
+	 q = REVEAL_POINTER(q->next_in_gc)) {
+	if (q->finalize)
+	    (*q->finalize)(q);
+    }
+}
+
+GC_API void GC_fiq_queue_init_inner(struct GC_fiq_queue *q,
+				    GC_fiq_finalizer_proc finalize,
+				    GCpriv_finalization_mark_proc mark_proc)
+{
+    if (!fiq_plugin)
+	fiq_plugin = GC_register_plugin_inner(fiq_inspect, fiq_finalize, 0);
+    q->live = HIDE_POINTER(0);
+    q->dead = 0;
+    q->dead_end = &(q->dead);
+    q->finalize = finalize;
+    q->mark_proc = mark_proc;
+    q->next_in_gc = fiq_queue_link;
+    fiq_queue_link = HIDE_POINTER(q);
+}
+
+GC_API void GC_fiq_queue_init(struct GC_fiq_queue *q,
+			      GC_fiq_finalizer_proc finalize)
+{
+    DCL_LOCK_STATE;
+#   ifdef THREADS
+	LOCK();
+#   endif
+    GC_fiq_queue_init_inner(q, finalize, GC_normal_finalize_mark_proc);
+#   ifdef THREADS
+	UNLOCK();
+#   endif
+}
+
+GC_API void GC_fiq_queue_init_ignore_self(struct GC_fiq_queue *q,
+					  GC_fiq_finalizer_proc finalize)
+{
+    DCL_LOCK_STATE;
+#   ifdef THREADS
+	LOCK();
+#   endif
+    GC_fiq_queue_init_inner(q, finalize, GC_ignore_self_finalize_mark_proc);
+#   ifdef THREADS
+	UNLOCK();
+#   endif
+}
+
+GC_API void GC_fiq_queue_init_no_order(struct GC_fiq_queue *q,
+				       GC_fiq_finalizer_proc finalize)
+{
+    DCL_LOCK_STATE;
+#   ifdef THREADS
+	LOCK();
+#   endif
+    GC_fiq_queue_init_inner(q, finalize, GC_null_finalize_mark_proc);
+#   ifdef THREADS
+	UNLOCK();
+#   endif
+}
+
+GC_API void *GC_fiq_malloc(struct GC_fiq_queue *q, size_t size)
+{
+    DCL_LOCK_STATE;
+    void *p = GC_malloc(size + sizeof(void *));
+#   ifdef THREADS
+	LOCK();
+#   endif
+    *(GC_word *)p = q->live;
+    q->live = HIDE_POINTER(p);
+#   ifdef THREADS
+	UNLOCK();
+#   endif
+    return (GC_word *)p + 1;
+}
+
+GC_API void *GC_fiq_malloc_atomic(struct GC_fiq_queue *q, size_t size)
+{
+    DCL_LOCK_STATE;
+    GC_word *p = GC_malloc_atomic(size + sizeof(void *));
+#   ifdef THREADS
+	LOCK();
+#   endif
+    *p = q->live;
+    q->live = HIDE_POINTER(p);
+#   ifdef THREADS
+	UNLOCK();
+#   endif
+    return p + 1;
+}
+
+GC_API void *GC_fiq_queue_pop(struct GC_fiq_queue *q)
+{
+    GC_word *p;
+    DCL_LOCK_STATE;
+#   ifdef THREADS
+	LOCK();
+#   endif
+    p = (GC_word *)q->dead;
+    q->dead = *p;
+    if (p == q->dead_end)
+	q->dead_end = & q->dead;
+#   ifdef THREADS
+	UNLOCK();
+#   endif
+    return p + 1;
+}
diff -Naur gc6.4/contrib/Makefile.am gc6.4-fiq/contrib/Makefile.am
--- gc6.4/contrib/Makefile.am	1970-01-01 01:00:00.000000000 +0100
+++ gc6.4-fiq/contrib/Makefile.am	2005-02-02 19:04:19.000000000 +0100
@@ -0,0 +1,15 @@
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/include -I$(top_builddir)/include
+
+contribdir = $(includedir)/gc/contrib
+contrib_HEADERS = wpmap.h diag.h
+lib_LTLIBRARIES = libgc-contrib.la
+libgc_contrib_la_SOURCES = wpmap.c
+
+TESTS = fiq_t0 wpmap_t0
+check_PROGRAMS = fiq_t0 wpmap_t0
+
+wpmap_t0_SOURCES = wpmap_t0.c
+wpmap_t0_LDADD = libgc-contrib.la ../libgc.la
+fiq_t0_SOURCES = fiq_t0.c
+fiq_t0_LDADD = ../libgc.la
diff -Naur gc6.4/contrib/diag.h gc6.4-fiq/contrib/diag.h
--- gc6.4/contrib/diag.h	1970-01-01 01:00:00.000000000 +0100
+++ gc6.4-fiq/contrib/diag.h	2005-01-22 14:58:26.000000000 +0100
@@ -0,0 +1,26 @@
+#ifndef _GC_DIAG_H
+#define _GC_DIAG_H
+
+#include "private/gc_priv.h"
+#ifdef __cplusplus
+#endif
+
+#define GC_err_die0(msg) \
+	do { GC_err_printf0(msg); ABORT("irrecoverable error"); } while (0)
+#define GC_err_die1(msg, arg0) \
+	do { GC_err_printf1(msg, arg0); \
+	     ABORT("irrecoverable error"); } while (0)
+#define GC_err_die2(msg, arg0, arg1) \
+	do { GC_err_printf2(msg, arg0, arg1); \
+	     ABORT("irrecoverable error"); } while (0)
+#define GC_err_die3(msg, arg0, arg1, arg2) \
+	do { GC_err_printf3(msg, arg0, arg1, arg2); \
+	     ABORT("irrecoverable error"); } while (0)
+#define GC_err_die4(msg, arg0, arg1, arg2, arg3) \
+	do { GC_err_printf4(msg, arg0, arg1, arg2, arg3); \
+	     ABORT("irrecoverable error"); } while (0)
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff -Naur gc6.4/contrib/fiq_t0.c gc6.4-fiq/contrib/fiq_t0.c
--- gc6.4/contrib/fiq_t0.c	1970-01-01 01:00:00.000000000 +0100
+++ gc6.4-fiq/contrib/fiq_t0.c	2005-02-02 08:52:17.000000000 +0100
@@ -0,0 +1,101 @@
+#include "gc_fiq.h"
+#include "gc.h"
+#include <stdio.h>
+#include <stdint.h>
+
+#define TEST_THREAD_CNT 16
+#define TEST_REPEAT_CNT 256
+#define TEST_OBJ_CNT 1024
+
+#define test_assert(test)					\
+	((test)							\
+	 ? (void)0						\
+	 : (fprintf(stderr, "Assertion '"#test"' failed.\n"),	\
+	    ++test_error_cnt))
+
+#define test_assert_eq(lhs, rhs) test_assert_eq_impl(lhs, rhs, #lhs, #rhs)
+
+static int test_error_cnt = 0;
+
+void
+test_assert_eq_impl(long lhs, long rhs,
+		    char const *str_lhs, char const *str_rhs)
+{
+    if (lhs != rhs) {
+	fprintf(stderr, "error: %s = %ld != %s = %ld\n",
+		str_lhs, lhs, str_rhs, rhs);
+	++test_error_cnt;
+    }
+}
+
+typedef struct test_s *test_t;
+struct test_s
+{
+    int biscuit;
+    struct GC_fiq_queue q;
+    int *obj_arr[TEST_OBJ_CNT];
+};
+
+void *
+test_thread(void *ign)
+{
+    int i;
+    test_t t = GC_NEW(struct test_s);
+    int fin_cnt = 0;
+    int dead_cnt = 0;
+
+    GC_fiq_queue_init(&t->q, NULL);
+
+    for (i = 0; i < TEST_OBJ_CNT; ++i) {
+	t->obj_arr[i] = GC_fiq_malloc(&t->q, sizeof(int));
+	*t->obj_arr[i] = i;
+    }
+    GC_gcollect();
+    GC_invoke_finalizers();
+
+    test_assert(GC_fiq_queue_is_empty(&t->q));
+    for (i = 0; i < TEST_OBJ_CNT; i += 2)
+	t->obj_arr[i] = (void *)~(uintptr_t)t->obj_arr[i];
+
+    GC_gcollect();
+    GC_invoke_finalizers();
+
+    for (i = 0; i < TEST_OBJ_CNT; i += 2)
+	if (GC_fiq_obj_is_dead((void *)~(uintptr_t)t->obj_arr[i]))
+	    ++dead_cnt;
+    for (i = 1; i < TEST_OBJ_CNT; i += 2)
+	test_assert(!GC_fiq_obj_is_dead(t->obj_arr[i]));
+
+    while (!GC_fiq_queue_is_empty(&t->q)) {
+	int *o = GC_fiq_queue_pop(&t->q);
+	test_assert((*o & 1) == 0);
+	++fin_cnt;
+    }
+    test_assert(fin_cnt >= dead_cnt && dead_cnt > fin_cnt/2);
+    if (dead_cnt < fin_cnt)
+	printf("dead_cnt = %d != %d = fin_cnt\n", dead_cnt, fin_cnt);
+    test_assert(TEST_OBJ_CNT/3 <= fin_cnt && fin_cnt <= TEST_OBJ_CNT/2);
+    if (fin_cnt < TEST_OBJ_CNT/2)
+	printf("Only %d out of %d objects finalized.\n",
+	       fin_cnt, TEST_OBJ_CNT/2);
+    printf("Leaving thread.\n");
+    return NULL;
+}
+
+int
+main(int argc, char **argv)
+{
+    pthread_t th_idr[TEST_THREAD_CNT - 1];
+    int i;
+    GC_init();
+    for (i = 0; i < TEST_THREAD_CNT - 1; ++i)
+	if (GC_pthread_create(&th_idr[i], NULL, test_thread, NULL) != 0) {
+	    perror(argv[0]);
+	    exit(2);
+	}
+    test_thread(NULL);
+    for (i = 0; i < TEST_THREAD_CNT - 1; ++i)
+	GC_pthread_join(th_idr[i], NULL);
+    printf("All done, %d errors.\n", test_error_cnt);
+    return test_error_cnt? 2 : 0;
+}
diff -Naur gc6.4/contrib/wpmap.h gc6.4-fiq/contrib/wpmap.h
--- gc6.4/contrib/wpmap.h	1970-01-01 01:00:00.000000000 +0100
+++ gc6.4-fiq/contrib/wpmap.h	2005-02-01 20:00:02.000000000 +0100
@@ -0,0 +1,67 @@
+/* The weak pointer property maps defined here are hash maps from weak
+ * pointers to arbitrary objects.  The pointer-typed keys of the maps
+ * are not seen by the collector from the map.  When the object which
+ * a key points to is ready to be recycled by the collector, the
+ * corresponding association is removed from the map.
+ *
+ * GC_WPMAP_IS_THREAD_LOCAL can only be defined if it is guaranteed
+ * that GC_wpmap_t is used locally within a thread and the collector
+ * runs in the same thread.  Thus, it is only feasible for single
+ * threaded applications. */
+
+#ifndef _GC_WPMAP_H
+#define _GC_WPMAP_H
+
+#define GC_I_HIDE_POINTERS 1
+#include "gc.h"
+#include "gc_weakptr.h"
+#include <stdlib.h>
+#include <pthread.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef size_t GC_wpmap_hash_t;
+typedef struct GC_wpmap_node_s *GC_wpmap_node_t;
+typedef struct GC_wpmap_s *GC_wpmap_t;
+
+struct GC_wpmap_node_s
+{
+    GC_wpmap_node_t next;
+    GC_weakptr key;
+};
+
+struct GC_wpmap_s
+{
+    GC_wpmap_node_t *arr;
+    size_t size;
+    size_t mask;
+    pthread_mutex_t mutex;
+};
+
+/* Construct a weak pointer property map. */
+void GC_wpmap_cct(GC_wpmap_t map);
+
+/* Return a collectable and constructed weak pointer property map. */
+GC_wpmap_t GC_wpmap_new(void);
+
+#define GC_wpmap_size(map) ((map)->size)
+
+void GC_wpmap_prune(GC_wpmap_t map);
+
+int GC_wpmap_insert_mem(GC_wpmap_t map, void *key,
+			size_t value_size, void *value_slot_out);
+
+int GC_wpmap_insert_ptr(GC_wpmap_t map, void *key, void *value_ptr_slot_out);
+
+int GC_wpmap_erase(GC_wpmap_t map, void *key);
+
+void *GC_wpmap_find_mem(GC_wpmap_t map, void *key);
+
+void *GC_wpmap_find_ptr(GC_wpmap_t map, void *key);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff -Naur gc6.4/contrib/wpmap.c gc6.4-fiq/contrib/wpmap.c
--- gc6.4/contrib/wpmap.c	1970-01-01 01:00:00.000000000 +0100
+++ gc6.4-fiq/contrib/wpmap.c	2005-02-10 18:58:53.000000000 +0100
@@ -0,0 +1,193 @@
+#include "wpmap.h"
+#include "diag.h"
+#include <stddef.h>
+
+#define HASHX(key) (((key) ^ ((key) >> 7)) - (((key) >> 11) ^ ((key) >> 19)))
+#define HASH(key) HASHX((GC_wpmap_hash_t)key)
+#define INITIAL_CAPACITY 4
+#define MIN_FILL_NOM 1
+#define MIN_FILL_DENOM 3
+#define MAX_FILL_NOM 2
+#define MAX_FILL_DENOM 3
+
+static void GC_wpmap_set_capacity(GC_wpmap_t map, size_t new_cap);
+
+void
+GC_wpmap_lock(GC_wpmap_t map)
+{
+    int err = pthread_mutex_lock(&map->mutex);
+    if (err != 0)
+	GC_err_die1("Unexpected problems with locking GC_wpmap_t mutex. "
+		    "(err = %d)", err);
+}
+#define GC_wpmap_unlock(map) pthread_mutex_unlock(&(map)->mutex)
+
+void
+GC_wpmap_cct(GC_wpmap_t map)
+{
+    map->arr = GC_malloc(INITIAL_CAPACITY*sizeof(GC_wpmap_node_t));
+    if (map->arr == NULL)
+	GC_err_die1("out of memory in GC_wpmap_t constructor for %p.", map);
+    map->size = 0;
+    map->mask = INITIAL_CAPACITY - 1;
+    memset(map->arr, 0, INITIAL_CAPACITY*sizeof(GC_wpmap_node_t));
+    pthread_mutex_init(&map->mutex, NULL);
+}
+
+GC_wpmap_t
+GC_wpmap_new()
+{
+    GC_wpmap_t map = GC_malloc(sizeof(struct GC_wpmap_s));
+    if (!map)
+	GC_err_die0("Out of memory in GC_wpmap_new.");
+    GC_wpmap_cct(map);
+    return map;
+}
+
+/* Lock must be held. */
+static void
+GC_wpmap_set_capacity(GC_wpmap_t map, size_t new_cap)
+{
+    size_t old_cap = map->mask + 1;
+    GC_wpmap_node_t *old_arr = map->arr;
+    GC_wpmap_node_t *old_arr_end = old_arr + old_cap;
+    size_t new_mask = new_cap - 1;
+    GC_wpmap_node_t *new_arr;
+    GC_wpmap_node_t *head;
+
+    GC_ASSERT((new_cap & new_mask) == 0); /* capacity must be power of 2 */
+    new_arr = GC_malloc(new_cap*sizeof(GC_wpmap_node_t));
+    if (!new_arr)
+	GC_err_die2("Out of memory while expanding GC_wpmap_t at %p to %ld "
+		    "entries.", map, new_cap);
+    memset(new_arr, 0, new_cap*sizeof(GC_wpmap_node_t));
+    for (head = old_arr; head != old_arr_end; ++head) {
+	GC_wpmap_node_t node = *head;
+	while (node) {
+	    void *key = GC_weakptr_get(&node->key);
+	    GC_wpmap_node_t next_node = node->next;
+	    GC_wpmap_hash_t hash = HASH(key);
+	    GC_wpmap_node_t *dst_node = &new_arr[hash & new_mask];
+	    node->next = *dst_node;
+	    *dst_node = node;
+	    node = next_node;
+	}
+    }
+    memset(old_arr, 0, old_cap*sizeof(GC_wpmap_node_t));
+    map->arr = new_arr;
+    map->mask = new_mask;
+}
+
+void
+GC_wpmap_prune(GC_wpmap_t map)
+{
+    GC_wpmap_node_t *head;
+    GC_wpmap_node_t *arr_end;
+    GC_wpmap_lock(map);
+    arr_end = map->arr + map->mask + 1;
+    for (head = map->arr; head != arr_end; ++head) {
+	GC_wpmap_node_t *node = head;
+	while (*node) {
+	    if (GC_weakptr_get(&(*node)->key) == NULL) {
+		*node = (*node)->next;
+		--map->size;
+	    }
+	    else
+		node = &(*node)->next;
+	}
+    }
+    if (map->size*MIN_FILL_DENOM < map->mask*MIN_FILL_NOM)
+	GC_wpmap_set_capacity(map, (map->mask + 1)/2);
+    GC_wpmap_unlock(map);
+}
+
+int
+GC_wpmap_insert_mem(GC_wpmap_t map, void *key,
+		    size_t value_size, void *value_slot_out)
+{
+    GC_wpmap_node_t *dst_node, node;
+    GC_wpmap_lock(map);
+    if (map->size*MAX_FILL_DENOM > map->mask*MAX_FILL_NOM)
+	GC_wpmap_set_capacity(map, (map->mask + 1)*2);
+    dst_node = &map->arr[HASH(key) & map->mask];
+    for (node = *dst_node; node; node = node->next) {
+	if (key == GC_weakptr_get(&node->key)) {
+	    GC_wpmap_unlock(map);
+	    *(void **)value_slot_out = (void *)(node + 1);
+	    return 0;
+	}
+    }
+    node = GC_malloc(sizeof(struct GC_wpmap_node_s) + value_size);
+    if (!node)
+	GC_err_die1("Out of memory while allocating node in GC_wpmap_t at %p",
+		    map);
+    node->next = *dst_node;
+    GC_weakptr_init(&node->key, key);
+    *dst_node = node;
+    ++map->size;
+    GC_wpmap_unlock(map);
+    *(void **)value_slot_out = (void *)(node + 1);
+    return 1;
+}
+
+int
+GC_wpmap_insert_ptr(GC_wpmap_t map, void *key, void *ptr_slot)
+{
+    return GC_wpmap_insert_mem(map, key, sizeof(void *), ptr_slot);
+}
+
+int
+GC_wpmap_erase(GC_wpmap_t map, void *key)
+{
+    GC_wpmap_node_t *node;
+    GC_wpmap_lock(map);
+    node = &map->arr[HASH(key) & map->mask];
+    while (*node) {
+	if (GC_weakptr_get(&(*node)->key) == key) {
+	    *node = (*node)->next;
+	    --map->size;
+	    if (map->size*MIN_FILL_DENOM < map->mask*MIN_FILL_NOM &&
+		map->mask > INITIAL_CAPACITY - 1)
+		GC_wpmap_set_capacity(map, (map->mask + 1)/2);
+	    GC_wpmap_unlock(map);
+	    return 1;
+	}
+	node = &(*node)->next;
+    }
+    GC_wpmap_unlock(map);
+    return 0;
+}
+
+void *
+GC_wpmap_find_mem(GC_wpmap_t map, void *key)
+{
+    GC_wpmap_node_t node;
+    GC_wpmap_lock(map);
+    node = map->arr[HASH(key) & map->mask];
+    while (node) {
+	if (GC_weakptr_get(&node->key) == key) {
+	    GC_wpmap_unlock(map);
+	    return (void *)(node + 1);
+	}
+	node = node->next;
+    }
+    GC_wpmap_unlock(map);
+    return NULL;
+}
+
+void *
+GC_wpmap_find_ptr(GC_wpmap_t map, void *key)
+{
+    GC_wpmap_node_t node;
+    GC_wpmap_lock(map);
+    node = map->arr[HASH(key) & map->mask];
+    while (node) {
+	if (GC_weakptr_get(&node->key) == key) {
+	    GC_wpmap_unlock(map);
+	    return *(void **)(node + 1);
+	}
+	node = node->next;
+    }
+    GC_wpmap_unlock(map);
+    return NULL;
+}
diff -Naur gc6.4/contrib/wpmap_t0.c gc6.4-fiq/contrib/wpmap_t0.c
--- gc6.4/contrib/wpmap_t0.c	1970-01-01 01:00:00.000000000 +0100
+++ gc6.4-fiq/contrib/wpmap_t0.c	2005-02-01 21:27:58.000000000 +0100
@@ -0,0 +1,118 @@
+#include "wpmap.h"
+#include <stdio.h>
+
+#define TEST_THREAD_CNT 16
+#define TEST_PTR_CNT 1024
+#define TEST_REPEAT_CNT 128
+
+#define test_assert(test)					\
+	((test)							\
+	 ? (void)0						\
+	 : (fprintf(stderr, "Assertion '"#test"' failed.\n"),	\
+	    ++test_error_cnt))
+
+#define test_assert_eq(lhs, rhs) test_assert_eq_impl(lhs, rhs, #lhs, #rhs)
+
+static int test_error_cnt = 0;
+
+void
+test_assert_eq_impl(long lhs, long rhs,
+		    char const *str_lhs, char const *str_rhs)
+{
+    if (lhs != rhs) {
+	fprintf(stderr, "error: %s = %ld != %s = %ld\n",
+		str_lhs, lhs, str_rhs, rhs);
+	++test_error_cnt;
+    }
+}
+
+void
+prop_set(GC_wpmap_t prop_map, void *ptr, int n)
+{
+    int *slot;
+    if (GC_wpmap_insert_mem(prop_map, ptr, sizeof(int), &slot))
+	*slot = n;
+}
+
+void
+prop_clear(GC_wpmap_t prop_map, void *ptr)
+{
+    GC_wpmap_erase(prop_map, ptr);
+}
+
+int
+prop_get(GC_wpmap_t prop_map, void *ptr)
+{
+    int *slot = GC_wpmap_find_mem(prop_map, ptr);
+    return slot? *slot : -1;
+}
+
+void
+test()
+{
+    size_t i;
+    int *ptr_arr[TEST_PTR_CNT];
+
+    /* Create a large number of associations */
+    GC_wpmap_t prop_map = GC_wpmap_new();
+    for (i = 0; i < TEST_PTR_CNT; ++i) {
+	ptr_arr[i] = GC_malloc(sizeof(int));
+	*ptr_arr[i] = i;
+	prop_set(prop_map, ptr_arr[i], -i);
+    }
+    GC_gcollect();
+    GC_invoke_finalizers();
+    GC_wpmap_prune(prop_map);
+    for (i = 0; i < TEST_PTR_CNT; ++i)
+	test_assert_eq(prop_get(prop_map, ptr_arr[i]), -*ptr_arr[i]);
+    test_assert_eq(GC_wpmap_size(prop_map), TEST_PTR_CNT);
+
+    /* Clear some pointers and collect */
+    for (i = 0; i < TEST_PTR_CNT; i += 2)
+	ptr_arr[i] = NULL;
+    GC_gcollect();
+    GC_invoke_finalizers();
+    GC_wpmap_prune(prop_map);
+    test_assert(GC_wpmap_size(prop_map) >= TEST_PTR_CNT/2 &&
+		GC_wpmap_size(prop_map) < 2*TEST_PTR_CNT/3);
+    if (GC_wpmap_size(prop_map) > TEST_PTR_CNT/2)
+	printf("%lu properties still in map, only %d are needed\n",
+	       (long)GC_wpmap_size(prop_map), TEST_PTR_CNT/2);
+    for (i = 1; i < TEST_PTR_CNT; i += 2)
+	test_assert_eq(prop_get(prop_map, ptr_arr[i]), -*ptr_arr[i]);
+
+    /* Explicitely erase some associations */
+    for (i = 1; i < TEST_PTR_CNT/2; i += 2)
+	prop_clear(prop_map, ptr_arr[i]);
+    for (i = TEST_PTR_CNT/2 + 1; i < TEST_PTR_CNT; i += 2)
+	test_assert_eq(prop_get(prop_map, ptr_arr[i]), -*ptr_arr[i]);
+}
+
+void *
+test_thread(void *clos_arg)
+{
+    int i;
+    printf("Entering test_thread()\n");
+    for (i = 0; i < TEST_REPEAT_CNT/TEST_THREAD_CNT; ++i)
+	test();
+    printf("Leaving test_thread()\n");
+    return NULL;
+}
+
+int
+main(int argc, char **argv)
+{
+    pthread_t th_idr[TEST_THREAD_CNT - 1];
+    int i;
+    GC_init();
+    for (i = 0; i < TEST_THREAD_CNT - 1; ++i)
+	if (GC_pthread_create(&th_idr[i], NULL, test_thread, NULL) != 0) {
+	    perror(argv[0]);
+	    exit(2);
+	}
+    test_thread(NULL);
+    for (i = 0; i < TEST_THREAD_CNT - 1; ++i)
+	GC_pthread_join(th_idr[i], NULL);
+    printf("All done, %d errors.\n", test_error_cnt);
+    return test_error_cnt? 2 : 0;
+}


More information about the Gc mailing list