[kaffe] CVS kaffe (hkraemer): fixes and improvement for the garbage collector

Kaffe CVS Kaffe Mailing List <kaffe@kaffe.org>
Sun Sep 28 12:50:03 2003


PatchSet 4071 
Date: 2003/09/28 19:47:40
Author: hkraemer
Branch: HEAD
Tag: (none) 
Log:
fixes and improvement for the garbage collector

* fixed the endless loop in startGC when running javalayer 0.3.0
* (hopefully) made the heap management more efficient

Members: 
	ChangeLog:1.1666->1.1667 
	kaffe/kaffevm/mem/gc-incremental.c:1.67->1.68 
	kaffe/kaffevm/mem/gc-mem.c:1.46->1.47 
	kaffe/kaffevm/mem/gc-mem.h:1.16->1.17 

Index: kaffe/ChangeLog
diff -u kaffe/ChangeLog:1.1666 kaffe/ChangeLog:1.1667
--- kaffe/ChangeLog:1.1666	Sat Sep 27 12:37:46 2003
+++ kaffe/ChangeLog	Sun Sep 28 19:47:40 2003
@@ -1,3 +1,25 @@
+2003-09-28  Helmer Kraemer  <hkraemer@freenet.de>
+
+	* kaffe/kaffevm/mem/gc-incremental.c:
+	(startGC, finaliserMan) properly deal with objects on the finaliser
+	list when starting a gc pass (fixes endless loop)
+	(createGC) initialise the heap and reserve primitive pages for OOM
+	handling
+	
+	* kaffe/kaffevm/gc-mem.h: (struct gc_block) added pnext and pprev
+	fields for management of primitive blocks; removed inuse field
+	(GCBLOCKINUSE) new macro to test whether a gc_block is used
+
+	replaced all uses of the inuse field by calls to the GCBLOCKINUSE
+	macro
+
+	* kaffe/kaffevm/gc-mem.c: (gc_get_prim_freelist, gc_add_to_prim_freelist,
+	gc_remove_from_prim_freelist, gc_merge_with_successor) new helper
+	methods for management of primitive blocks
+	(gc_primitive_alloc, gc_primitive_free) manage primitive blocks
+	using a best fit algorithm
+	(gc_heap_grow) don't forget to lock the gc_heap_lock 
+	
 2003-09-27  Guilhem Lavaux <guilhem@kaffe.org>
 
 	* libraries/javalib/kjc.jar: Fix for path method invocation.
Index: kaffe/kaffe/kaffevm/mem/gc-incremental.c
diff -u kaffe/kaffe/kaffevm/mem/gc-incremental.c:1.67 kaffe/kaffe/kaffevm/mem/gc-incremental.c:1.68
--- kaffe/kaffe/kaffevm/mem/gc-incremental.c:1.67	Fri Aug 22 11:42:15 2003
+++ kaffe/kaffe/kaffevm/mem/gc-incremental.c	Sun Sep 28 19:47:41 2003
@@ -228,7 +228,7 @@
 	uintp p = (uintp) UTOMEM(unit) - gc_heap_base;
 	int idx;
 
-	if (!(p & (MEMALIGN - 1)) && p < gc_heap_range && info->inuse) {
+	if (!(p & (MEMALIGN - 1)) && p < gc_heap_range && GCBLOCKINUSE(info)) {
 		/* Make sure 'unit' refers to the beginning of an
 		 * object.  We do this by making sure it is correctly
 		 * aligned within the block.
@@ -246,7 +246,7 @@
 static inline void
 markObjectDontCheck(gc_unit *unit, gc_block *info, int idx)
 {
-	/* If object's been traced before, don't do it again */
+	/* If the object has been traced before, don't do it again. */
 	if (GC_GET_COLOUR(info, idx) != GC_COLOUR_WHITE) {
 		return;
 	}
@@ -382,15 +382,15 @@
 	}
 
 	info = GCMEM2BLOCK(mem);
-	if (!info->inuse) {
+	if (!GCBLOCKINUSE(info)) {
 		/* go down block list to find out whether we were hitting
 		 * in a large object
 		 */
-		while (!info->inuse && (uintp)info > (uintp)gc_block_base) {
+		while (!GCBLOCKINUSE(info) && (uintp)info > (uintp)gc_block_base) {
 			info--;
 		}
 		/* must be a large block, hence nr must be 1 */
-		if (!info->inuse || info->nr != 1) {
+		if (!GCBLOCKINUSE(info) || info->nr != 1) {
 			return (0);
 		}
 	}
@@ -663,6 +663,8 @@
 startGC(Collector *gcif)
 {
 	gc_unit* unit;
+	gc_block* info;
+	int idx;
 
 	gcStats.freedmem = 0;
 	gcStats.freedobj = 0;
@@ -685,10 +687,25 @@
 	/* measure time */
 	startTiming(&gc_time, "gctime-scan");
 
-	/* Walk all objects on the finalizer list */
+	/*
+	 * Since objects whose finaliser has to be run need to
+	 * be kept alive, we have to mark them here. They will
+	 * be put back into the finalise list later on during
+	 * the gc pass.
+	 *
+	 * Since these objects are treated like garbage, we have
+	 * to set their colour to white before marking them.
+	 */
 	while (gclists[finalise].cnext != &gclists[finalise]) {
 		unit = gclists[finalise].cnext;
-		gcMarkObject(gcif, UTOMEM(unit));
+		info = GCMEM2BLOCK(unit);
+		idx = GCMEM2IDX(info, unit);
+
+		GC_SET_COLOUR (info, idx, GC_COLOUR_WHITE);
+		gcStats.finalobj -= 1;
+		gcStats.finalmem -= GCBLOCKSIZE(info);
+
+		markObjectDontCheck(unit, info, idx); 
 	}
 
 	(*walkRootSet)(gcif);
@@ -851,27 +868,52 @@
 		}
 		assert(finalRunning == true);
 
+		/*
+		 * Loop until the list of objects whose finaliser needs to be run is empty
+		 * [ checking the condition without holding a lock is ok, since we're the only
+		 * thread removing elements from the list (the list can never shrink during
+		 * a gc pass) ].
+		 *
+		 * According to the spec, the finalisers have to be run without any user
+		 * visible locks held. Therefore, we must temporarily release the finman
+		 * lock and may not hold the gc_lock while running the finalisers as they
+		 * are exposed to the user by java.lang.Runtime.
+		 * 
+		 * In addition, we must prevent an object and everything it references from
+		 * being collected while the finaliser is run (since we can't hold the gc_lock,
+		 * there may be several gc passes in the meantime). To do so, we keep the
+		 * object in the finalise list and only remove it from there when its
+		 * finaliser is done (simply adding the object to the grey list while its
+		 * finaliser is run only works as long as there's at most one gc pass).
+		 *
+		 * In order to determine the finaliser of an object, we have to access the
+		 * gc_block that contains it and its index. Doing this without holding a
+		 * lock only works as long as both, the gc_blocks and the indices of the
+		 * objects in a gc_block, are constant.
+		 */
 		while (gclists[finalise].cnext != &gclists[finalise]) {
-			lockStaticMutex(&gc_lock);
 			unit = gclists[finalise].cnext;
-			UREMOVELIST(unit);
-			UAPPENDLIST(gclists[grey], unit);
-
 			info = GCMEM2BLOCK(unit);
 			idx = GCMEM2IDX(info, unit);
+
+			/* Call finaliser */
+			unlockStaticMutex(&finman);
+			(*gcFunctions[GC_GET_FUNCS(info,idx)].final)(gcif, UTOMEM(unit));
+			lockStaticMutex(&finman);
+
+			/* and remove unit from the finaliser list */
+			lockStaticMutex(&gc_lock);
+			UREMOVELIST(unit);
+			UAPPENDLIST(gclists[nofin_white], unit);
+
 			gcStats.finalmem -= GCBLOCKSIZE(info);
 			gcStats.finalobj -= 1;
 
 			assert(GC_GET_STATE(info,idx) == GC_STATE_INFINALIZE);
 			/* Objects are only finalised once */
 			GC_SET_STATE(info, idx, GC_STATE_FINALIZED);
-			GC_SET_COLOUR(info, idx, GC_COLOUR_GREY);
+			GC_SET_COLOUR(info, idx, GC_COLOUR_WHITE);
 			unlockStaticMutex(&gc_lock);
-
-			/* Call finaliser */
-			unlockStaticMutex(&finman);
-			(*gcFunctions[GC_GET_FUNCS(info,idx)].final)(gcif, UTOMEM(unit));
-			lockStaticMutex(&finman);
 		}
 
 		/* Wake up anyone waiting for the finalizer to finish */
@@ -999,6 +1041,10 @@
 
 			case 2:
 				/* Grow the heap */
+				DBG (GCSYSALLOC, dprintf ("growing heap by %u bytes of type %s (%2.1f%% free)\n", 
+							  size, gcFunctions[fidx].description,
+							  (1.0 - (gcStats.totalmem / (double)gc_heap_total)) * 100.0); )
+				
 				gc_heap_grow(size);
 				break;
 
@@ -1068,9 +1114,6 @@
 			UAPPENDLIST(gclists[nofin_white], unit);
 		}
 	}
-	if (!reserve) {
-		reserve = gc_primitive_reserve();
-	}
 
 	/* It is not safe to allocate java objects the first time
 	 * gcMalloc is called, but it should be safe after gcEnable
@@ -1163,6 +1206,8 @@
 	idx = GCMEM2IDX(info, unit);
 	osize = GCBLOCKSIZE(info) - sizeof(gc_unit);
 
+	assert(GC_GET_FUNCS(info, idx) == fidx);
+
 	/* Can only handled fixed objects at the moment */
 	assert(GC_GET_COLOUR(info, idx) == GC_COLOUR_FIXED);
 	info = 0;
@@ -1398,6 +1443,10 @@
 	URESETLIST(gclists[fin_black]);
 	URESETLIST(gclists[finalise]);
 	gc_obj.collector.ops = &GC_Ops;
+
+	gc_heap_initialise ();
+	reserve = gc_primitive_reserve ();
+
 	return (&gc_obj.collector);
 }
 
Index: kaffe/kaffe/kaffevm/mem/gc-mem.c
diff -u kaffe/kaffe/kaffevm/mem/gc-mem.c:1.46 kaffe/kaffe/kaffevm/mem/gc-mem.c:1.47
--- kaffe/kaffe/kaffevm/mem/gc-mem.c:1.46	Fri Aug 22 11:42:15 2003
+++ kaffe/kaffe/kaffevm/mem/gc-mem.c	Sun Sep 28 19:47:41 2003
@@ -42,10 +42,6 @@
 static gc_block* gc_primitive_alloc(size_t);
 void gc_primitive_free(gc_block*);
 
-uintp gc_heap_base;
-uintp gc_block_base;
-uintp gc_heap_range;
-
 /**
  * A preallocated block for small objects.
  *
@@ -98,13 +94,14 @@
 } sztable[MAX_SMALL_OBJECT_SIZE+1];
 static int max_freelist;
 
-static gc_block* gc_prim_freelist;
 static size_t max_small_object_size;
 
 size_t gc_heap_total;		/* current size of the heap */
 size_t gc_heap_allocation_size;	/* amount of memory by which to grow heap */
 size_t gc_heap_initial_size;	/* amount of memory to initially allocate */
 size_t gc_heap_limit;		/* maximum size to which heap should grow */
+uintp gc_heap_base;
+uintp gc_heap_range;
 
 #ifndef gc_pgsize
 size_t gc_pgsize;
@@ -133,6 +130,7 @@
 		totalsmallobjs, totalslack, totalslack/(double)totalsmallobjs);
 }
 
+
 /*
  * check whether the heap is still in a consistent state
  */
@@ -148,7 +146,7 @@
 		} else {
 			gc_freeobj* mem = blk->free;
 
-			assert(blk->inuse);
+			assert(GCBLOCKINUSE(blk));
 			assert(blk->avail < blk->nr);
 			assert(blk->funcs == (uint8*)GCBLOCK2BASE(blk));
 			assert(blk->state == (uint8*)(blk->funcs + blk->nr));
@@ -167,7 +165,6 @@
 /*
  * Initialise allocator.
  */
-static
 void
 gc_heap_initialise(void)
 {
@@ -288,7 +285,6 @@
 void*
 gc_heap_malloc(size_t sz)
 {
-	static int gc_heap_init = 0;
 	size_t lnr;
 	gc_freeobj* mem = 0;
 	gc_block** mptr;
@@ -296,14 +292,6 @@
 	size_t nsz;
 	int iLockRoot;
 
-	/* Initialise GC heap first time in - we must assume single threaded
-	 * operation here so we can do the lock initialising.
-	 */
-	if (gc_heap_init == 0) {
-		gc_heap_initialise();
-		gc_heap_init = 1;
-	}
-
 	lockStaticMutex(&gc_heap_lock);
 
 DBG(SLACKANAL,
@@ -466,7 +454,7 @@
 	}
 	else {
 		/* Calculate true size of block */
-		msz = info->size + ROUNDUPALIGN(1);
+		msz = info->size + 2 + ROUNDUPALIGN(1);
 		msz = ROUNDUPPAGESIZE(msz);
 		info->size = msz;
 		gc_primitive_free(info);
@@ -578,9 +566,30 @@
 /*
  * Primitive block management:  Allocating and freeing whole pages.
  *
- * Unused pages may be marked unreadable.  This is only done when
- * compiled with KAFFE_VMDEBUG.
- */ 
+ * Each primitive block of the heap consists of one or more contiguous
+ * pages. Pages of unused primitive blocks are marked unreadable when
+ * kaffe is compiled with debugging enabled. Whether a block is in use
+ * can be determined by its nr field: when it's in use, its nr field
+ * will be > 0.
+ *
+ * All primitive blocks are chained through their pnext / pprev fields,
+ * no matter whether or not they are in use. This makes the necessary
+ * check for mergable blocks as cheap as possible. Merging small blocks
+ * is necessary so that single unused primitive blocks in the heap are
+ * always as large as possible. The first block in the list is stored
+ * in gc_block_base, the last block in the list is gc_last_block.
+ *
+ * In order to speed up the search for the primitive block that fits
+ * a given allocation request best, small primitive blocks are stored
+ * in several lists (one per size). If no primitive blocks of a given
+ * size are left, a larger one is splitted instead. 
+ */
+#define GC_PRIM_LIST_COUNT 20
+
+uintp gc_block_base;
+static gc_block *gc_last_block;
+static gc_block *gc_prim_freelist[GC_PRIM_LIST_COUNT+1];
+
 
 #ifndef PROT_NONE
 #define PROT_NONE 0
@@ -596,22 +605,68 @@
 #define NO_PROT  PROT_NONE
 #endif
 
-/* Mark this block as in-use */
+/* Mark a primitive block as used */
 static inline void 
 gc_block_add(gc_block *b)
 {
-	b->inuse = 1;
+	b->nr = 1;
 	mprotect(GCBLOCK2BASE(b), b->size, ALL_PROT);
 }
 
-/* Mark this block as free */
+/* Mark a primitive block as unused */
 static inline void 
 gc_block_rm(gc_block *b)
 {
-	b->inuse = 0;
+	b->nr = 0;
 	mprotect(GCBLOCK2BASE(b), b->size, NO_PROT);
 }
 
+/* return the prim list blk belongs to */
+static inline gc_block **
+gc_get_prim_freelist (gc_block *blk)
+{
+	size_t sz = blk->size >> gc_pgbits;
+
+	if (sz <= GC_PRIM_LIST_COUNT)
+	{
+		return &gc_prim_freelist[sz-1];
+	}
+
+	return &gc_prim_freelist[GC_PRIM_LIST_COUNT];
+}
+
+/* add a primitive block to the correct freelist */
+static inline void
+gc_add_to_prim_freelist(gc_block *blk)
+{
+	gc_block **list = gc_get_prim_freelist (blk);
+
+	/* insert the block int the list, sorting by ascending addresses */
+	while (*list && blk > *list)
+	{
+		list = &(*list)->next;
+	}
+
+	if (*list) {
+		(*list)->free = (gc_freeobj *)&blk->next;
+	}
+
+	blk->next = *list;
+	*list = blk;
+	blk->free = (gc_freeobj *)list;
+}
+
+/* remove a primitive block from its freelist */
+static inline void
+gc_remove_from_prim_freelist(gc_block *blk)
+{
+	*( (gc_block **) blk->free ) = blk->next;
+
+	if (blk->next) {
+		blk->next->free = blk->free;
+	}
+}
+ 
 /*
  * Allocate a block of memory from the free list or, failing that, the
  * system pool.
@@ -620,127 +675,165 @@
 gc_block*
 gc_primitive_alloc(size_t sz)
 {
-	gc_block* ptr;
-	gc_block** pptr;
+	size_t diff = 0;
+	gc_block* best_fit = NULL;
+	size_t i = sz >> gc_pgbits;
 
 	assert(sz % gc_pgsize == 0);
 
-	for (pptr = &gc_prim_freelist; *pptr != 0; pptr = &ptr->next) {
-		ptr = *pptr;
-		/* First fit */
-		if (sz <= ptr->size) {
-			size_t left;
-			/* If there's more than a page left, split it */
-			left = ptr->size - sz;
-			if (left >= gc_pgsize) {
-				gc_block* nptr;
-
-				ptr->size = sz;
-				nptr = GCBLOCKEND(ptr);
-				nptr->size = left;
+	DBG(GCPRIM, dprintf("\ngc_primitive_alloc: got to allocate 0x%x bytes\n", sz); )
 
-				DBG(GCDIAG, nptr->magic = GC_MAGIC);
+	/* try freelists for small primitive blocks first */
+	if (i <= GC_PRIM_LIST_COUNT) {
+		for (i-=1; i<GC_PRIM_LIST_COUNT; i++) {
+			if (gc_prim_freelist[i]) {
+				best_fit = gc_prim_freelist[i]; 
+				diff = gc_prim_freelist[i]->size - sz;
+				break;
+			}
+		}
+	}
 
-				nptr->next = ptr->next;
-				ptr->next = nptr;
+	/* if that fails, try the big remaining list */
+	if (!best_fit) {
+		gc_block *ptr;
+		for (ptr = gc_prim_freelist[GC_PRIM_LIST_COUNT]; ptr != 0; ptr=ptr->next) {
+
+			/* Best fit */
+			if (sz == ptr->size) {
+				diff = 0;
+				best_fit = ptr;
+				break;
+			} else if (sz < ptr->size) {
+				size_t left = ptr->size - sz;
+		
+				if (best_fit==NULL || left<diff) {
+					diff = left;
+					best_fit = ptr;
+				}		
 			}
-			*pptr = ptr->next;
-DBG(GCPRIM,		dprintf("gc_primitive_alloc: %d bytes from freelist @ %p\n", ptr->size, ptr); )
-			gc_block_add(ptr);
-			return (ptr);
 		}
 	}
 
+	/* if we found a block, remove it from the list and check if splitting is necessary */
+	if (best_fit) {
+		gc_remove_from_prim_freelist (best_fit);
+
+		DBG(GCPRIM, dprintf ("gc_primitive_alloc: found best_fit %p diff 0x%x (0x%x - 0x%x)\n",
+				     best_fit, diff, best_fit->size, sz); )
+		assert ( diff % gc_pgsize == 0 );
+
+		if (diff > 0) {
+			gc_block *nptr;
+
+			best_fit->size = sz;
+		
+			nptr = GCBLOCKEND(best_fit);
+			nptr->size = diff;
+			gc_block_rm (nptr);
+
+			DBG(GCPRIM, dprintf ("gc_primitive_alloc: splitted remaining 0x%x bytes @ %p\n", diff, nptr); )
+
+			DBG(GCDIAG, nptr->magic = GC_MAGIC);
+
+			/* maintain list of primitive blocks */
+			nptr->pnext = best_fit->pnext;
+			nptr->pprev = best_fit;
+
+			best_fit->pnext = nptr;
+
+			if (nptr->pnext) {
+				nptr->pnext->pprev = nptr;
+			} else {
+				gc_last_block = nptr;
+			}
+
+			/* and add nptr to one of the freelists */
+			gc_add_to_prim_freelist (nptr);
+		}
+
+DBG(GCPRIM,	dprintf("gc_primitive_alloc: 0x%x bytes from freelist @ %p\n", best_fit->size, best_fit); )
+		gc_block_add(best_fit);
+		return (best_fit);
+	}
+DBG(GCPRIM,	dprintf("gc_primitive_alloc: no suitable block found!\n"); )
+
 	/* Nothing found on free list */
 	return (0);
 }
 
 /*
+ * merge a primitive block with its successor.
+ */
+static inline void
+gc_merge_with_successor (gc_block *blk)
+{
+	gc_block *next_blk = blk->pnext;
+
+	assert (next_blk);
+
+	blk->size += next_blk->size;
+	blk->pnext = next_blk->pnext;
+
+	/*
+	 * if the merged block has a successor, update its pprev field.
+	 * otherwise, the merged block is the last block in the primitive
+	 * chain.
+	 */
+	if (blk->pnext) {
+		blk->pnext->pprev = blk;
+	} else {
+		gc_last_block = blk;
+	}
+}
+
+
+/*
  * Return a block of memory to the free list.
  */
 void
 gc_primitive_free(gc_block* mem)
 {
-	gc_block* lptr;
-	gc_block* nptr;
+	gc_block *blk;
 
 	assert(mem->size % gc_pgsize == 0);
 
 	/* Remove from object hash */
 	gc_block_rm(mem);
-	mem->next = 0;
 
-	if(mem < gc_prim_freelist || gc_prim_freelist == 0) {
-		/* If this block is directly before the first block on the
-		 * freelist, merge it into that block.  Otherwise just
-		 * attached it to the beginning.
-		 */
-		if (GCBLOCKEND(mem) == gc_prim_freelist) {
-DBG(GCPRIM,	dprintf("gc_primitive_free: Merging (%d,%p) beginning of freelist\n", mem->size, mem); )
-			mem->size += gc_prim_freelist->size;
-			mem->next = gc_prim_freelist->next;
-		}
-		else {
-DBG(GCPRIM,	dprintf("gc_primitive_free: Prepending (%d,%p) beginning of freelist\n", mem->size, mem); )
-			mem->next = gc_prim_freelist;
-		}
-		gc_prim_freelist = mem;
-		return;
-	}
+	DBG(GCPRIM, dprintf ("\ngc_primitive_free: freeing block %p (%x bytes, %x)\n", mem, mem->size, mem->size >> gc_pgbits); )
 
-	/* Search the freelist for the logical place to put this block */
-	lptr = gc_prim_freelist;
-	while (lptr->next != 0) {
-		nptr = lptr->next;
-		if (mem > lptr && mem < nptr) {
-			/* Block goes here in the logical scheme of things.
-			 * Work out how to merge it with those which come
-			 * before and after.
-			 */
-			if (GCBLOCKEND(lptr) == mem) {
-				if (GCBLOCKEND(mem) == nptr) {
-					/* Merge with last and next */
-DBG(GCPRIM,				dprintf("gc_primitive_free: Merging (%d,%p) into list\n", mem->size, mem); )
-					lptr->size += mem->size + nptr->size;
-					lptr->next = nptr->next;
-				}
-				else {
-					/* Merge with last but not next */
-DBG(GCPRIM,				dprintf("gc_primitive_free: Merging (%d,%p) with last in list\n", mem->size, mem); )
-					lptr->size += mem->size;
-				}
-			}
-			else {
-				if (GCBLOCKEND(mem) == nptr) {
-					/* Merge with next but not last */
-DBG(GCPRIM,				dprintf("gc_primitive_free: Merging (%d,%p) with next in list\n", mem->size, mem); )
-					mem->size += nptr->size;
-					mem->next = nptr->next;
-					lptr->next = mem;
-				}
-				else {
-					/* Wont merge with either */
-DBG(GCPRIM,				dprintf("gc_primitive_free: Inserting (%d,%p) into list\n", mem->size, mem); )
-					mem->next = nptr;
-					lptr->next = mem;
-				}
-			}
-			return;
-		}
-		lptr = nptr;
-	}
-
-	/* If 'mem' goes directly after the last block, merge it in.
-	 * Otherwise, just add in onto the list at the end.
+	/*
+	 * Test whether this block is mergable with its successor.
+	 * We need to do the GCBLOCKEND check, since the heap may not be a continuous
+	 * memory area and thus two consecutive blocks need not be mergable. 
 	 */
-	if (GCBLOCKEND(lptr) == mem) {
-DBG(GCPRIM,	dprintf("gc_primitive_free: Merge (%d,%p) onto last in list\n", mem->size, mem); )
-		lptr->size += mem->size;
+	if ((blk=mem->pnext) &&
+	    !GCBLOCKINUSE(blk) &&
+	    GCBLOCKEND(mem)==blk) {
+		DBG(GCPRIM, dprintf ("gc_primitive_free: merging %p with its successor (%p, %u)\n", mem, blk, blk->size);)
+
+		gc_remove_from_prim_freelist(blk);
+
+		gc_merge_with_successor (mem);
 	}
-	else {
-DBG(GCPRIM,	dprintf("gc_primitive_free: Append (%d,%p) onto last in list\n", mem->size, mem); )
-		lptr->next = mem;
+
+	if ((blk=mem->pprev) &&
+	    !GCBLOCKINUSE(blk) &&
+	    GCBLOCKEND(blk)==mem) {
+		DBG(GCPRIM, dprintf ("gc_primitive_free: merging %p with its predecessor (%p, %u)\n", mem, blk, blk->size); )
+
+		gc_remove_from_prim_freelist(blk);
+
+		mem = blk;
+
+		gc_merge_with_successor (mem);
 	}
+
+	gc_add_to_prim_freelist (mem);
+
+	DBG(GCPRIM, dprintf ("gc_primitive_free: added 0x%x bytes @ %p to freelist %u @ %p\n", mem->size, mem,
+			     gc_get_prim_freelist(mem)-&gc_prim_freelist[0], gc_get_prim_freelist(mem)); )
 }
 
 /*
@@ -851,9 +944,8 @@
 	static uintp last_addr;
 
 	if (!gc_block_base) {
-		nblocks = (gc_heap_limit>>gc_pgbits);
+		nblocks = (gc_heap_limit+gc_pgsize-1)>>gc_pgbits;
 
-		nblocks += nblocks/4;
 		gc_block_base = (uintp) malloc(nblocks * sizeof(gc_block));
 		if (!gc_block_base) return 0;
 		memset((void *)gc_block_base, 0, nblocks * sizeof(gc_block));
@@ -920,7 +1012,6 @@
 		   There should be no gc_block *'s on any stack
 		   now. */ 
 		if (gc_block_base != old_blocks) {
-			extern gc_block *gc_prim_freelist;
 			int i;
 			gc_block *b = (void *) gc_block_base;
 			uintp delta = gc_block_base - old_blocks;
@@ -932,7 +1023,8 @@
 			memset(b + onb, 0,
 			       (nblocks - onb) * sizeof(gc_block));
 
-			R(gc_prim_freelist);
+			for (i = 0; i<=GC_PRIM_LIST_COUNT; i++)
+				R(gc_prim_freelist[i]);
 
 			for (i = 0; freelist[i].list != (void*)-1; i++) 
 				R(freelist[i].list);
@@ -960,6 +1052,7 @@
 gc_heap_grow(size_t sz)
 {
 	gc_block* blk;
+	int iLockRoot;
 
 	if (GC_SMALL_OBJECT(sz)) {
 		sz = gc_pgsize;
@@ -974,6 +1067,8 @@
 
 	assert(sz % gc_pgsize == 0);
 
+	lockStaticMutex(&gc_heap_lock);
+
 	if (gc_heap_total == gc_heap_limit) {
 		return (0);
 	} else if (gc_heap_total + sz > gc_heap_limit) {
@@ -1002,11 +1097,18 @@
 	DBG(GCDIAG, blk->magic = GC_MAGIC);
 	blk->size = sz;
 
-	/* Attach block to object hash */
-	gc_block_add(blk);
+	/* maintain list of primitive blocks */
+	if (gc_last_block) {
+		gc_last_block->pnext = blk;
+		blk->pprev = gc_last_block;
+	} else {
+		gc_last_block = blk;
+	}
 
 	/* Free block into the system */
 	gc_primitive_free(blk);
+
+	unlockStaticMutex(&gc_heap_lock);
 
 	return (blk);
 }
Index: kaffe/kaffe/kaffevm/mem/gc-mem.h
diff -u kaffe/kaffe/kaffevm/mem/gc-mem.h:1.16 kaffe/kaffe/kaffevm/mem/gc-mem.h:1.17
--- kaffe/kaffe/kaffevm/mem/gc-mem.h:1.16	Fri Jun 20 13:01:08 2003
+++ kaffe/kaffe/kaffevm/mem/gc-mem.h	Sun Sep 28 19:47:41 2003
@@ -72,6 +72,7 @@
 
 /* ------------------------------------------------------------------------ */
 
+extern void	gc_heap_initialise (void);
 extern void*	gc_heap_malloc(size_t);    
 extern void	gc_heap_free(void*);
 
@@ -94,8 +95,8 @@
 #endif
 	struct _gc_freeobj*	free;	/* Next free sub-block */
 	struct _gc_block*	next;	/* Next block in prim/small freelist */
-	struct _gc_block*	nfree;	/* Next block on sub-freelist */
-	uint32			inuse;	/* 1bit! Is block allocated? */
+	struct _gc_block*	pnext;	/* next primitive block */
+	struct _gc_block*	pprev;	/* previous primitive block */
 	uint32			size;	/* Size of objects in this block */
 	uint16			nr;	/* Nr of objects in block */
 	uint16			avail;	/* Nr of objects available in block */
@@ -111,9 +112,13 @@
 
 #define	GC_MAGIC		0xD0DECADE
 
-#define GCBLOCK_LIVE		((gc_block *) -1) /* block->next when alloced*/
-
 #define GC_BLOCKS		((gc_block *) gc_block_base)
+
+/**
+ * Tests whether a block is in use.
+ *
+ */
+#define GCBLOCKINUSE(B)		((B)->nr > 0)
 
 /**
  * Evaluates to the array that contains the states of the objects contained in @B.