Hi Simon,
>I don't think there's a bug in the current implementation. You'll
>notice that when we coalesce with the previous block, we find the block
>head before checking its flags, so only the block head needs to have the
>BF_FREE flag set.
Duh, this is of course spot on... I thought this had to be done in the
patch I just sent you because I lazily initialize the bdescr and need to know
the first and last block truly are properly reflecting an accurate
coalescable state, but of course as long as the head block has its
flags set and the tail blocks has link to head and number of blocks set
to zero, it's all fine... Just had Knuth's boundary tags on my mind far
too much ;-) Here's a better patch [I hope]!
Cheers
Andy
On Mon, 4 Jul 2005, Simon Marlow wrote:
>Hi Andy,
>
>Thanks for the patch. I don't have time to look at it right now, but
>I'll hopefully get to it soon.
>
>I don't think there's a bug in the current implementation. You'll
>notice that when we coalesce with the previous block, we find the block
>head before checking its flags, so only the block head needs to have the
>BF_FREE flag set.
>
>Cheers,
> Simon
>
>On 01 July 2005 12:06, Andrew Cheadle wrote:
>
>> Hi Simon,
>>
>> Here's a HEAD patch for the block allocator that removes (I think ;-])
>> the more subtle O(N^2) portion of the allocator namely when performing
>> block descriptor initialisation.
>>
>> I should also comment that I think there's a bug in the merge of
>> my original patch with HEAD where BF_ALLOCATED was switched to
>> BF_FREE - I think I'm correct in saying that if you do this, then the
>> last block descriptor in a block chunk must also have it's flags set
>> to BF_FREE
>> so that predecessor coalescing may occur (in addition on allocation
>> these must also be cleared). I've remedied this (I hope ;-]) in the
>> attached patch.
>>
>> Let me know your thoughts!
>>
>> Cheers
>>
>> Andy
>>
>> On Sun, 5 Jun 2005, Andrew Cheadle wrote:
>>
>>> Hi Simon,
>>>
>>> Here's a patch for the block allocator that reduces the coalescing
>>> algorithm from O(N^2) to N.
>>>
>>> I created the mods and tested them on my 6.2 builds and then prop'd
>>> them to the current 6.4 CVS HEAD. When I get a chance and HEAD is
>>> unb0rked I'll test them on it. Hopefully the attached patch as is
>>> fine though ;-) Any problems/bugs/idiocy let me know!
>>>
>>> Cheers
>>>
>>> Andy
>>>
>>> *********************************************************************
>>> * Andrew Cheadle email: [EMAIL PROTECTED] *
>>> * Department of Computing http://www.doc.ic.ac.uk/~amc4/ *
>>> * Imperial College *
>>> * University of London *
>>> *********************************************************************
>>
>> *********************************************************************
>> * Andrew Cheadle email: [EMAIL PROTECTED] *
>> * Department of Computing http://www.doc.ic.ac.uk/~amc4/ *
>> * Imperial College *
>> * University of London *
>> *********************************************************************
>
>
*********************************************************************
* Andrew Cheadle email: [EMAIL PROTECTED] *
* Department of Computing http://www.doc.ic.ac.uk/~amc4/ *
* Imperial College *
* University of London *
*********************************************************************
diff -r -rcs --exclude=CVS ../clean/fptools/ghc/includes/Block.h
ghc/includes/Block.h
*** ../clean/fptools/ghc/includes/Block.h 2005-06-13 13:29:48.000000000
+0100
--- ghc/includes/Block.h 2005-06-30 10:22:48.652917328 +0100
***************
*** 87,92 ****
--- 87,94 ----
#define BF_COMPACTED 8
/* Block is free, and on the free list */
#define BF_FREE 16
+ /* Block is in process of being freed, and on a temporary free list */
+ #define BF_FREEING 32
/* Finding the block descriptor for a given block --------------------------
*/
***************
*** 159,164 ****
--- 161,180 ----
*list = bd;
}
+ INLINE_HEADER void
+ dbl_unlink_from(bdescr *bd, bdescr **list)
+ {
+ if (bd->u.back) {
+ bd->u.back->link = bd->link;
+ }
+ if (bd->link) {
+ bd->link->u.back = bd->u.back;
+ }
+ if (*list == bd) {
+ *list = bd->link;
+ }
+ }
+
/* Initialisation ----------------------------------------------------------
*/
extern void initBlockAllocator(void);
***************
*** 172,177 ****
--- 188,194 ----
extern void freeGroup(bdescr *p);
extern void freeChain(bdescr *p);
+ extern void prepareToFreeChain(bdescr **list_head, bdescr **list_tail, bdescr
*p);
/* Round a value to megablocks ---------------------------------------------
*/
diff -r -rcs --exclude=CVS ../clean/fptools/ghc/rts/Arena.c ghc/rts/Arena.c
*** ../clean/fptools/ghc/rts/Arena.c 2005-02-23 10:59:17.000000000 +0000
--- ghc/rts/Arena.c 2005-06-28 12:04:14.000000000 +0100
***************
*** 107,114 ****
next = bd->link;
arena_blocks -= bd->blocks;
ASSERT(arena_blocks >= 0);
- freeGroup(bd);
}
stgFree(arena);
}
--- 107,114 ----
next = bd->link;
arena_blocks -= bd->blocks;
ASSERT(arena_blocks >= 0);
}
+ freeChain(arena->current);
stgFree(arena);
}
diff -r -rcs --exclude=CVS ../clean/fptools/ghc/rts/BlockAlloc.c
ghc/rts/BlockAlloc.c
*** ../clean/fptools/ghc/rts/BlockAlloc.c 2005-06-13 13:29:49.000000000
+0100
--- ghc/rts/BlockAlloc.c 2005-07-05 17:24:16.712565472 +0100
***************
*** 27,32 ****
--- 27,33 ----
static void initMBlock(void *mblock);
static bdescr *allocMegaGroup(nat mblocks);
static void freeMegaGroup(bdescr *bd);
+ static void initTempFreeListGroup(bdescr *list);
static bdescr *free_list = NULL;
***************
*** 36,42 ****
void initBlockAllocator(void)
{
! // The free list starts off NULL
}
/*
-----------------------------------------------------------------------------
--- 37,43 ----
void initBlockAllocator(void)
{
! // The free list starts off NULL
}
/*
-----------------------------------------------------------------------------
***************
*** 46,60 ****
STATIC_INLINE void
initGroupTail(nat n, bdescr *head, bdescr *tail)
{
! bdescr *bd;
! nat i;
! for (i=0, bd = tail; i < n; i++, bd++) {
! bd->flags = 0;
! bd->free = 0;
! bd->blocks = 0;
! bd->link = head;
! }
}
STATIC_INLINE void
--- 47,61 ----
STATIC_INLINE void
initGroupTail(nat n, bdescr *head, bdescr *tail)
{
! bdescr *bd;
! nat i;
! for (i=0, bd = tail; i < n; i++, bd++) {
! bd->flags = 0;
! bd->free = 0;
! bd->blocks = 0;
! bd->link = head;
! }
}
STATIC_INLINE void
***************
*** 65,71 ****
head->free = head->start;
head->link = NULL;
head->flags = 0;
! initGroupTail( n-1, head, head+1 );
}
}
--- 66,72 ----
head->free = head->start;
head->link = NULL;
head->flags = 0;
! initGroupTail(n - 1, head, head + 1);
}
}
***************
*** 206,289 ****
/* coalesce the group p with its predecessor and successor groups, if possible
*
* Returns NULL if no coalescing was done, otherwise returns a
! * pointer to the newly enlarged group p.
*/
STATIC_INLINE bdescr *
! coalesce(bdescr *p)
{
! bdescr *first, *q, *result = NULL;
! /* Get first megablock descriptor */
! first = FIRST_BDESCR(MBLOCK_ROUND_DOWN(p->start));
! /* Attempt to coalesce with predecessor if not the first block */
! if (p != first) {
! q = p - 1;
! if (!q->blocks) { // not a block head?
! q = q->link; // find the head.
! }
! /* Predecessor is free? */
! if (q->flags & BF_FREE) {
! q->blocks += p->blocks;
! initGroupTail( p->blocks, q, p );
! p = result = q;
! }
}
! /* Attempt to coalesce with successor if not the last block */
! q = p + p->blocks;
! if (q != first + BLOCKS_PER_MBLOCK) {
! /* Successor is free */
! if (q->flags & BF_FREE) {
! if (result) {
! /* p is on free_list, q is on free_list, unlink
! * q completely and patch up list
! */
! if (q->u.back) {
! q->u.back->link = q->link;
! }
! if (q->link) {
! q->link->u.back = q->u.back;
! }
! if (free_list == q) {
! free_list = q->link;
! }
! } else {
! /* p is not on free_list just assume q's links */
! p->u.back = q->u.back;
! if (p->u.back) {
! p->u.back->link = p;
! }
! p->link = q->link;
! if (p->link) {
! p->link->u.back = p;
! }
! if (q == free_list) {
! free_list = p;
! free_list->u.back = NULL;
! }
! }
!
! p->blocks += q->blocks;
! initGroupTail( q->blocks, p, q );
! result = p;
! }
}
! return result;
}
void
freeGroup(bdescr *p)
{
/* are we dealing with a megablock group? */
if (p->blocks > BLOCKS_PER_MBLOCK) {
freeMegaGroup(p);
return;
}
! p->flags = BF_FREE;
p->u.back = NULL;
p->link = NULL;
p->step = NULL;
--- 207,302 ----
/* coalesce the group p with its predecessor and successor groups, if possible
*
* Returns NULL if no coalescing was done, otherwise returns a
! * pointer to the newly enlarged group p moved from free_list to
! * list if necessary.
*/
STATIC_INLINE bdescr *
! coalesce(bdescr *p, bdescr **list)
{
! bdescr *first, *q, *result = NULL;
! /* Get first megablock descriptor */
! first = FIRST_BDESCR(MBLOCK_ROUND_DOWN(p->start));
! /* Attempt to coalesce with predecessor if not the first block */
! if (p != first) {
! q = p - 1;
! if (!q->blocks) { // not a block head?
! q = q->link; // find the head.
}
+ /* Predecessor is free? */
+ if (q->flags & BF_FREE || q->flags & BF_FREEING) {
+ q->blocks += p->blocks;
+ p = q + (q->blocks - 1);
+ p->link = q;
+ p->blocks = 0;
+ if (q->flags & BF_FREE) {
+ q->flags = BF_FREEING;
+ dbl_unlink_from(q, &free_list);
+ dbl_link_onto(q, list);
+ }
+ p = result = q;
+ }
+ }
! /* Attempt to coalesce with successor if not the last block */
! q = p + p->blocks;
! if (q != first + BLOCKS_PER_MBLOCK) {
! /* Successor is free */
! if (q->flags & BF_FREE) {
! /* q is on free_list, unlink q */
! dbl_unlink_from(q, &free_list);
! if (!result) {
! dbl_link_onto(p, list);
! }
! p->blocks += q->blocks;
! q = p + (p->blocks - 1);
! q->link = p;
! q->blocks = 0;
! result = p;
! } else if (q->flags & BF_FREEING) {
! if (result) {
! /* p is on list, q is on list, unlink q */
! dbl_unlink_from(q, list);
! } else {
! /* p is not on list just assume q's links */
! p->u.back = q->u.back;
! if (p->u.back) {
! p->u.back->link = p;
! }
! p->link = q->link;
! if (p->link) {
! p->link->u.back = p;
! }
! if (q == *list) {
! *list = p;
! (*list)->u.back = NULL;
! }
! }
! p->blocks += q->blocks;
! q = p + (p->blocks - 1);
! q->link = p;
! q->blocks = 0;
! result = p;
}
+ }
! return result;
}
void
freeGroup(bdescr *p)
{
+ bdescr *list = NULL;
+
/* are we dealing with a megablock group? */
if (p->blocks > BLOCKS_PER_MBLOCK) {
freeMegaGroup(p);
return;
}
! p->flags = BF_FREEING;
p->u.back = NULL;
p->link = NULL;
p->step = NULL;
***************
*** 291,298 ****
/* fill the block group with garbage if sanity checking is on */
IF_DEBUG(sanity,memset(p->start, 0xaa, p->blocks * BLOCK_SIZE));
! if (!coalesce(p)) {
dbl_link_onto(p, &free_list);
}
IF_DEBUG(sanity, checkFreeListSanity());
--- 304,346 ----
/* fill the block group with garbage if sanity checking is on */
IF_DEBUG(sanity,memset(p->start, 0xaa, p->blocks * BLOCK_SIZE));
! if (!coalesce(p, &list)) {
! p->flags = BF_FREE;
dbl_link_onto(p, &free_list);
+ } else {
+ initTempFreeListGroup(list);
+ }
+
+ IF_DEBUG(sanity, checkFreeListSanity());
+ }
+
+ static void
+ tempFreeGroup(bdescr *p, bdescr **list)
+ {
+ /* are we dealing with a megablock group? */
+ if (p->blocks > BLOCKS_PER_MBLOCK) {
+ nat n;
+ void *q = p;
+
+ n = ((bdescr *)q)->blocks * BLOCK_SIZE / MBLOCK_SIZE + 1;
+ for (; n > 0; q += MBLOCK_SIZE, n--) {
+ initMBlock(MBLOCK_ROUND_DOWN(q));
+ initGroup(BLOCKS_PER_MBLOCK, (bdescr *)q);
+ tempFreeGroup((bdescr *)q, list);
+ }
+ return;
+ }
+
+ p->flags = BF_FREEING;
+ p->u.back = NULL;
+ p->link = NULL;
+ p->step = NULL;
+ p->gen_no = 0;
+ /* fill the block group with garbage if sanity checking is on */
+ IF_DEBUG(sanity,memset(p->start, 0xaa, p->blocks * BLOCK_SIZE));
+
+ if (!coalesce(p, list)) {
+ dbl_link_onto(p, list);
}
IF_DEBUG(sanity, checkFreeListSanity());
***************
*** 301,306 ****
--- 349,355 ----
static void
freeMegaGroup(bdescr *p)
{
+ bdescr *temp_free_list = NULL;
nat n;
void *q = p;
***************
*** 308,326 ****
for (; n > 0; q += MBLOCK_SIZE, n--) {
initMBlock(MBLOCK_ROUND_DOWN(q));
initGroup(BLOCKS_PER_MBLOCK, (bdescr *)q);
! freeGroup((bdescr *)q);
}
}
void
freeChain(bdescr *bd)
{
! bdescr *next_bd;
while (bd != NULL) {
next_bd = bd->link;
! freeGroup(bd);
bd = next_bd;
}
}
static void
--- 357,397 ----
for (; n > 0; q += MBLOCK_SIZE, n--) {
initMBlock(MBLOCK_ROUND_DOWN(q));
initGroup(BLOCKS_PER_MBLOCK, (bdescr *)q);
! tempFreeGroup((bdescr *)q, &temp_free_list);
}
+
+ initTempFreeListGroup(temp_free_list);
}
void
freeChain(bdescr *bd)
{
! bdescr *next_bd, *temp_free_list = NULL;
!
while (bd != NULL) {
next_bd = bd->link;
! tempFreeGroup(bd, &temp_free_list);
bd = next_bd;
}
+
+ initTempFreeListGroup(temp_free_list);
+ }
+
+ void
+ prepareToFreeChain(bdescr **list_head, bdescr **list_tail, bdescr *bd)
+ {
+ if (!*list_tail) {
+ *list_head = bd;
+ } else {
+ (*list_tail)->link = bd;
+ }
+
+ if (bd) {
+ do {
+ *list_tail = bd;
+ bd = bd->link;
+ } while (bd);
+ }
}
static void
***************
*** 342,347 ****
--- 413,436 ----
}
}
+ static void
+ initTempFreeListGroup(bdescr *list)
+ {
+ bdescr *bd, *tail_bd = NULL;
+
+ for (bd = list; bd; bd = bd->link) {
+ bd->flags = BF_FREE;
+ initGroupTail(bd->blocks - 1, bd, bd + 1);
+ tail_bd = bd;
+ }
+
+ tail_bd->link = free_list;
+ if (free_list) {
+ free_list->u.back = tail_bd;
+ }
+ free_list = list;
+ }
+
/*
-----------------------------------------------------------------------------
Debugging
--------------------------------------------------------------------------
*/
***************
*** 350,362 ****
static void
checkWellFormedGroup(bdescr *bd)
{
! nat i;
! for (i = 1; i < bd->blocks; i++) {
! ASSERT(bd[i].blocks == 0);
! ASSERT(bd[i].free == 0);
! ASSERT(bd[i].link == bd);
! }
}
void
--- 439,451 ----
static void
checkWellFormedGroup(bdescr *bd)
{
! nat i;
! for (i = 1; i < bd->blocks; i++) {
! ASSERT(bd[i].blocks == 0);
! ASSERT(bd[i].free == 0);
! ASSERT(bd[i].link == bd);
! }
}
void
diff -r -rcs --exclude=CVS ../clean/fptools/ghc/rts/GC.c ghc/rts/GC.c
*** ../clean/fptools/ghc/rts/GC.c 2005-06-06 09:49:07.000000000 +0100
--- ghc/rts/GC.c 2005-06-30 10:47:08.000000000 +0100
***************
*** 300,306 ****
void
GarbageCollect ( void (*get_roots)(evac_fn), rtsBool force_major_gc )
{
! bdescr *bd;
step *stp;
lnat live, allocated, collected = 0, copied = 0;
lnat oldgen_saved_blocks = 0;
--- 300,306 ----
void
GarbageCollect ( void (*get_roots)(evac_fn), rtsBool force_major_gc )
{
! bdescr *bd, *free_bd_chain_hd = NULL, *free_bd_chain_tl = NULL;
step *stp;
lnat live, allocated, collected = 0, copied = 0;
lnat oldgen_saved_blocks = 0;
***************
*** 396,402 ****
// always has at least one block; this means we can avoid a check for
// NULL in recordMutable().
if (g != 0) {
! freeChain(generations[g].mut_list);
generations[g].mut_list = allocBlock();
}
--- 396,403 ----
// always has at least one block; this means we can avoid a check for
// NULL in recordMutable().
if (g != 0) {
! prepareToFreeChain(&free_bd_chain_hd,
! &free_bd_chain_tl, generations[g].mut_list);
generations[g].mut_list = allocBlock();
}
***************
*** 528,533 ****
--- 529,539 ----
for (g = RtsFlags.GcFlags.generations-1; g > N; g--) {
IF_PAR_DEBUG(verbose, printMutableList(&generations[g]));
scavenge_mutable_list(&generations[g]);
+ // free the old mut_list
+ prepareToFreeChain(&free_bd_chain_hd,
+ &free_bd_chain_tl,
+ generations[g].saved_mut_list);
+ generations[g].saved_mut_list = NULL;
evac_gen = g;
for (st = generations[g].n_steps-1; st >= 0; st--) {
scavenge(&generations[g].steps[st]);
***************
*** 777,783 ****
// add the new blocks to the block tally
stp->n_blocks += stp->n_to_blocks;
} else {
! freeChain(stp->blocks);
stp->blocks = stp->to_blocks;
stp->n_blocks = stp->n_to_blocks;
for (bd = stp->blocks; bd != NULL; bd = bd->link) {
--- 783,790 ----
// add the new blocks to the block tally
stp->n_blocks += stp->n_to_blocks;
} else {
! prepareToFreeChain(&free_bd_chain_hd,
! &free_bd_chain_tl, stp->blocks);
stp->blocks = stp->to_blocks;
stp->n_blocks = stp->n_to_blocks;
for (bd = stp->blocks; bd != NULL; bd = bd->link) {
***************
*** 793,803 ****
* collection from large_objects. Any objects left on
* large_objects list are therefore dead, so we free them here.
*/
! for (bd = stp->large_objects; bd != NULL; bd = next) {
! next = bd->link;
! freeGroup(bd);
! bd = next;
! }
// update the count of blocks used by large objects
for (bd = stp->scavenged_large_objects; bd != NULL; bd = bd->link) {
--- 800,807 ----
* collection from large_objects. Any objects left on
* large_objects list are therefore dead, so we free them here.
*/
! prepareToFreeChain(&free_bd_chain_hd,
! &free_bd_chain_tl, stp->large_objects);
// update the count of blocks used by large objects
for (bd = stp->scavenged_large_objects; bd != NULL; bd = bd->link) {
***************
*** 910,918 ****
* all have been copied into G0S1 now.
*/
if (small_alloc_list != NULL) {
! freeChain(small_alloc_list);
}
- small_alloc_list = NULL;
alloc_blocks = 0;
alloc_Hp = NULL;
alloc_HpLim = NULL;
--- 914,923 ----
* all have been copied into G0S1 now.
*/
if (small_alloc_list != NULL) {
! prepareToFreeChain(&free_bd_chain_hd,
! &free_bd_chain_tl, small_alloc_list);
! small_alloc_list = NULL;
}
alloc_blocks = 0;
alloc_Hp = NULL;
alloc_HpLim = NULL;
***************
*** 924,930 ****
/* Free the mark stack.
*/
if (mark_stack_bdescr != NULL) {
! freeGroup(mark_stack_bdescr);
}
/* Free any bitmaps.
--- 929,936 ----
/* Free the mark stack.
*/
if (mark_stack_bdescr != NULL) {
! prepareToFreeChain(&free_bd_chain_hd,
! &free_bd_chain_tl, mark_stack_bdescr);
}
/* Free any bitmaps.
***************
*** 933,939 ****
for (s = 0; s < generations[g].n_steps; s++) {
stp = &generations[g].steps[s];
if (stp->is_compacted && stp->bitmap != NULL) {
! freeGroup(stp->bitmap);
}
}
}
--- 939,946 ----
for (s = 0; s < generations[g].n_steps; s++) {
stp = &generations[g].steps[s];
if (stp->is_compacted && stp->bitmap != NULL) {
! prepareToFreeChain(&free_bd_chain_hd,
! &free_bd_chain_tl, stp->bitmap);
}
}
}
***************
*** 945,956 ****
nat blocks;
if (old_to_blocks != NULL) {
! freeChain(old_to_blocks);
}
for (bd = g0s0->to_blocks; bd != NULL; bd = bd->link) {
bd->flags = 0; // now from-space
}
/* For a two-space collector, we need to resize the nursery. */
/* set up a new nursery. Allocate a nursery size based on a
--- 952,967 ----
nat blocks;
if (old_to_blocks != NULL) {
! prepareToFreeChain(&free_bd_chain_hd,
! &free_bd_chain_tl, old_to_blocks);
}
for (bd = g0s0->to_blocks; bd != NULL; bd = bd->link) {
bd->flags = 0; // now from-space
}
+ /* Free all the blocks collected during the collection cycle */
+ freeChain(free_bd_chain_hd);
+
/* For a two-space collector, we need to resize the nursery. */
/* set up a new nursery. Allocate a nursery size based on a
***************
*** 997,1002 ****
--- 1008,1016 ----
* allocation area to make best use of the memory available.
*/
+ /* Free all the blocks collected during the collection cycle */
+ freeChain(free_bd_chain_hd);
+
if (RtsFlags.GcFlags.heapSizeSuggestion) {
long blocks;
nat needed = calcNeeded(); // approx blocks needed at next GC
***************
*** 3653,3662 ****
}
}
}
-
- // free the old mut_list
- freeChain(gen->saved_mut_list);
- gen->saved_mut_list = NULL;
}
--- 3667,3672 ----
diff -r -rcs --exclude=CVS ../clean/fptools/ghc/rts/Storage.c ghc/rts/Storage.c
*** ../clean/fptools/ghc/rts/Storage.c 2005-05-12 12:36:50.000000000 +0100
--- ghc/rts/Storage.c 2005-06-28 12:05:32.000000000 +0100
***************
*** 480,498 ****
stp->blocks = allocNursery(stp, stp->blocks, blocks-nursery_blocks);
}
else {
! bdescr *next_bd;
IF_DEBUG(gc, debugBelch("Decreasing size of nursery to %d blocks\n",
blocks));
bd = stp->blocks;
while (nursery_blocks > blocks) {
next_bd = bd->link;
next_bd->u.back = NULL;
nursery_blocks -= bd->blocks; // might be a large block
! freeGroup(bd);
bd = next_bd;
}
stp->blocks = bd;
// might have gone just under, by freeing a large block, so make
// up the difference.
--- 480,501 ----
stp->blocks = allocNursery(stp, stp->blocks, blocks-nursery_blocks);
}
else {
! bdescr *next_bd, *free_bd_chain = NULL;
IF_DEBUG(gc, debugBelch("Decreasing size of nursery to %d blocks\n",
blocks));
bd = stp->blocks;
+
while (nursery_blocks > blocks) {
next_bd = bd->link;
next_bd->u.back = NULL;
nursery_blocks -= bd->blocks; // might be a large block
! bd->link = free_bd_chain;
! free_bd_chain = bd;
bd = next_bd;
}
+ freeChain(free_bd_chain);
stp->blocks = bd;
// might have gone just under, by freeing a large block, so make
// up the difference.
_______________________________________________
Cvs-ghc mailing list
[EMAIL PROTECTED]
http://www.haskell.org/mailman/listinfo/cvs-ghc