[project @ 1999-02-05 15:25:01 by simonm]
[ghc-hetmet.git] / ghc / rts / GC.c
index a154012..3311550 100644 (file)
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
- * $Id: GC.c,v 1.15 1999/01/19 17:06:02 simonm Exp $
+ * $Id: GC.c,v 1.26 1999/02/05 15:25:07 simonm Exp $
  *
  * Two-space garbage collector
  *
@@ -20,6 +20,7 @@
 #include "DebugProf.h"
 #include "SchedAPI.h"
 #include "Weak.h"
+#include "StablePriv.h"
 
 StgCAF* enteredCAFs;
 
@@ -102,7 +103,8 @@ static void           scavenge_stack(StgPtr p, StgPtr stack_end);
 static void           scavenge_large(step *step);
 static void           scavenge(step *step);
 static void           scavenge_static(void);
-static StgMutClosure *scavenge_mutable_list(StgMutClosure *p, nat gen);
+static void           scavenge_mutable_list(generation *g);
+static void           scavenge_mut_once_list(generation *g);
 
 #ifdef DEBUG
 static void gcCAFs(void);
@@ -190,7 +192,7 @@ void GarbageCollect(void (*get_roots)(void))
    * zeroMutableList below).
    */
   if (major_gc) { 
-    zeroMutableList(generations[RtsFlags.GcFlags.generations-1].mut_list);
+    zeroMutableList(generations[RtsFlags.GcFlags.generations-1].mut_once_list);
   }
 
   /* Save the old to-space if we're doing a two-space collection
@@ -204,6 +206,7 @@ void GarbageCollect(void (*get_roots)(void))
    * collecting.
    */
   for (g = 0; g <= N; g++) {
+    generations[g].mut_once_list = END_MUT_LIST;
     generations[g].mut_list = END_MUT_LIST;
 
     for (s = 0; s < generations[g].n_steps; s++) {
@@ -282,19 +285,27 @@ void GarbageCollect(void (*get_roots)(void))
    * it has already been evaced to gen 2.
    */
   { 
-    StgMutClosure *tmp, **pp;
-    for (g = N+1; g < RtsFlags.GcFlags.generations; g++) {
+    int st;
+    for (g = RtsFlags.GcFlags.generations-1; g > N; g--) {
       generations[g].saved_mut_list = generations[g].mut_list;
       generations[g].mut_list = END_MUT_LIST;
     }
 
+    /* Do the mut-once lists first */
+    for (g = RtsFlags.GcFlags.generations-1; g > N; g--) {
+      scavenge_mut_once_list(&generations[g]);
+      evac_gen = g;
+      for (st = generations[g].n_steps-1; st >= 0; st--) {
+       scavenge(&generations[g].steps[st]);
+      }
+    }
+
     for (g = RtsFlags.GcFlags.generations-1; g > N; g--) {
-      tmp = scavenge_mutable_list(generations[g].saved_mut_list, g);
-      pp = &generations[g].mut_list;
-      while (*pp != END_MUT_LIST) {
-          pp = &(*pp)->mut_link;
+      scavenge_mutable_list(&generations[g]);
+      evac_gen = g;
+      for (st = generations[g].n_steps-1; st >= 0; st--) {
+       scavenge(&generations[g].steps[st]);
       }
-      *pp = tmp;
     }
   }
 
@@ -317,6 +328,10 @@ void GarbageCollect(void (*get_roots)(void))
   weak_ptr_list = NULL;
   weak_done = rtsFalse;
 
+  /* Mark the stable pointer table.
+   */
+  markStablePtrTable(major_gc);
+
 #ifdef INTERPRETER
   { 
       /* ToDo: To fix the caf leak, we need to make the commented out
@@ -368,18 +383,21 @@ void GarbageCollect(void (*get_roots)(void))
 
     /* scavenge each step in generations 0..maxgen */
     { 
-      int gen; 
+      int gen, st; 
+    loop2:
       for (gen = RtsFlags.GcFlags.generations-1; gen >= 0; gen--) {
-       for (s = 0; s < generations[gen].n_steps; s++) {
-         step = &generations[gen].steps[s];
+       for (st = generations[gen].n_steps-1; st >= 0 ; st--) {
+         step = &generations[gen].steps[st];
          evac_gen = gen;
          if (step->hp_bd != step->scan_bd || step->scan < step->hp) {
            scavenge(step);
            flag = rtsTrue;
+           goto loop2;
          }
          if (step->new_large_objects != NULL) {
            scavenge_large(step);
            flag = rtsTrue;
+           goto loop2;
          }
        }
       }
@@ -392,6 +410,10 @@ void GarbageCollect(void (*get_roots)(void))
     }
   }
 
+  /* Now see which stable names are still alive
+   */
+  gcStablePtrTable(major_gc);
+
   /* Set the maximum blocks for the oldest generation, based on twice
    * the amount of live data now, adjusted to fit the maximum heap
    * size if necessary.  
@@ -411,65 +433,10 @@ void GarbageCollect(void (*get_roots)(void))
             (int)oldest_gen->steps[0].to_blocks) < 
            (RtsFlags.GcFlags.pcFreeHeap *
             RtsFlags.GcFlags.maxHeapSize / 200)) {
+         heapOverflow();
        }
       }
     }
-  } else {
-    /* For a two-space collector, we need to resize the nursery. */
-
-    /* set up a new nursery.  Allocate a nursery size based on a
-     * function of the amount of live data (currently a factor of 2,
-     * should be configurable (ToDo)).  Use the blocks from the old
-     * nursery if possible, freeing up any left over blocks.
-     *
-     * If we get near the maximum heap size, then adjust our nursery
-     * size accordingly.  If the nursery is the same size as the live
-     * data (L), then we need 3L bytes.  We can reduce the size of the
-     * nursery to bring the required memory down near 2L bytes.
-     * 
-     * A normal 2-space collector would need 4L bytes to give the same
-     * performance we get from 3L bytes, reducing to the same
-     * performance at 2L bytes.  
-     */
-    nat blocks = g0s0->to_blocks;
-
-    if ( blocks * 4 > RtsFlags.GcFlags.maxHeapSize ) {
-      int adjusted_blocks;  /* signed on purpose */
-      int pc_free; 
-      
-      adjusted_blocks = (RtsFlags.GcFlags.maxHeapSize - 2 * blocks);
-      IF_DEBUG(gc, fprintf(stderr, "Near maximum heap size of 0x%x blocks, blocks = %d, adjusted to %d\n", RtsFlags.GcFlags.maxHeapSize, blocks, adjusted_blocks));
-      pc_free = adjusted_blocks * 100 / RtsFlags.GcFlags.maxHeapSize;
-      if (pc_free < RtsFlags.GcFlags.pcFreeHeap) /* might even be < 0 */ {
-       heapOverflow();
-      }
-      blocks = adjusted_blocks;
-      
-    } else {
-      blocks *= 2;
-      if (blocks < RtsFlags.GcFlags.minAllocAreaSize) {
-       blocks = RtsFlags.GcFlags.minAllocAreaSize;
-      }
-    }
-    
-    if (nursery_blocks < blocks) {
-      IF_DEBUG(gc, fprintf(stderr, "Increasing size of nursery to %d blocks\n", 
-                          blocks));
-      g0s0->blocks = allocNursery(g0s0->blocks, blocks-nursery_blocks);
-    } else {
-      bdescr *next_bd;
-      
-      IF_DEBUG(gc, fprintf(stderr, "Decreasing size of nursery to %d blocks\n", 
-                          blocks));
-      for (bd = g0s0->blocks; nursery_blocks > blocks; nursery_blocks--) {
-       next_bd = bd->link;
-       freeGroup(bd);
-       bd = next_bd;
-      }
-      g0s0->blocks = bd;
-    }
-
-    g0s0->n_blocks = nursery_blocks = blocks;
   }
 
   /* run through all the generations/steps and tidy up 
@@ -529,16 +496,13 @@ void GarbageCollect(void (*get_roots)(void))
         * between the maximum size of the oldest and youngest
         * generations.
         *
-        * max_blocks = alloc_area_size +  
-        *                 (oldgen_max_blocks - alloc_area_size) * G
-        *                 -----------------------------------------
-        *                              oldest_gen
+        * max_blocks =    oldgen_max_blocks * G
+        *                 ----------------------
+        *                      oldest_gen
         */
        if (g != 0) {
-         generations[g].max_blocks = 
-           RtsFlags.GcFlags.minAllocAreaSize +
-            (((oldest_gen->max_blocks - RtsFlags.GcFlags.minAllocAreaSize) * g)
-              / (RtsFlags.GcFlags.generations-1));
+         generations[g].max_blocks = (oldest_gen->max_blocks * g)
+              / (RtsFlags.GcFlags.generations-1);
        }
 
       /* for older generations... */
@@ -560,31 +524,97 @@ void GarbageCollect(void (*get_roots)(void))
     }
   }
   
+  /* Guess the amount of live data for stats. */
+  live = calcLive();
+
   /* Two-space collector:
    * Free the old to-space, and estimate the amount of live data.
    */
   if (RtsFlags.GcFlags.generations == 1) {
+    nat blocks;
+    
     if (old_to_space != NULL) {
       freeChain(old_to_space);
     }
-    live = g0s0->to_blocks * BLOCK_SIZE_W + 
-      ((lnat)g0s0->hp_bd->free - (lnat)g0s0->hp_bd->start) / sizeof(W_);
+    for (bd = g0s0->to_space; bd != NULL; bd = bd->link) {
+      bd->evacuated = 0;       /* now from-space */
+    }
 
-  /* Generational collector:
-   * estimate the amount of live data.
-   */
+    /* For a two-space collector, we need to resize the nursery. */
+    
+    /* set up a new nursery.  Allocate a nursery size based on a
+     * function of the amount of live data (currently a factor of 2,
+     * should be configurable (ToDo)).  Use the blocks from the old
+     * nursery if possible, freeing up any left over blocks.
+     *
+     * If we get near the maximum heap size, then adjust our nursery
+     * size accordingly.  If the nursery is the same size as the live
+     * data (L), then we need 3L bytes.  We can reduce the size of the
+     * nursery to bring the required memory down near 2L bytes.
+     * 
+     * A normal 2-space collector would need 4L bytes to give the same
+     * performance we get from 3L bytes, reducing to the same
+     * performance at 2L bytes.  
+     */
+    blocks = g0s0->n_blocks;
+
+    if ( blocks * RtsFlags.GcFlags.oldGenFactor * 2 > 
+        RtsFlags.GcFlags.maxHeapSize ) {
+      int adjusted_blocks;  /* signed on purpose */
+      int pc_free; 
+      
+      adjusted_blocks = (RtsFlags.GcFlags.maxHeapSize - 2 * blocks);
+      IF_DEBUG(gc, fprintf(stderr, "Near maximum heap size of 0x%x blocks, blocks = %d, adjusted to %d\n", RtsFlags.GcFlags.maxHeapSize, blocks, adjusted_blocks));
+      pc_free = adjusted_blocks * 100 / RtsFlags.GcFlags.maxHeapSize;
+      if (pc_free < RtsFlags.GcFlags.pcFreeHeap) /* might even be < 0 */ {
+       heapOverflow();
+      }
+      blocks = adjusted_blocks;
+      
+    } else {
+      blocks *= RtsFlags.GcFlags.oldGenFactor;
+      if (blocks < RtsFlags.GcFlags.minAllocAreaSize) {
+       blocks = RtsFlags.GcFlags.minAllocAreaSize;
+      }
+    }
+    resizeNursery(blocks);
+    
   } else {
-    live = 0;
-    for (g = 0; g < RtsFlags.GcFlags.generations; g++) {
-      for (s = 0; s < generations[g].n_steps; s++) {
-       /* approximate amount of live data (doesn't take into account slop
-        * at end of each block).  ToDo: this more accurately.
-        */
-       if (g == 0 && s == 0) { continue; }
-       step = &generations[g].steps[s];
-       live += step->n_blocks * BLOCK_SIZE_W + 
-         ((lnat)step->hp_bd->free -(lnat)step->hp_bd->start) / sizeof(W_);
+    /* Generational collector:
+     * If the user has given us a suggested heap size, adjust our
+     * allocation area to make best use of the memory available.
+     */
+
+    if (RtsFlags.GcFlags.heapSizeSuggestion) {
+      int blocks;
+      nat needed = calcNeeded();       /* approx blocks needed at next GC */
+
+      /* Guess how much will be live in generation 0 step 0 next time.
+       * A good approximation is the amount of data that was live this
+       * time:  this assumes (1) that the size of G0S0 will be roughly
+       * the same as last time, and (2) that the promotion rate will be
+       * constant.
+       *
+       * If we don't know how much was live in G0S0 (because there's no
+       * step 1), then assume 30% (which is usually an overestimate).
+       */
+      if (g0->n_steps == 1) {
+       needed += (g0s0->n_blocks * 30) / 100;
+      } else {
+       needed += g0->steps[1].n_blocks;
       }
+
+      /* Now we have a rough guess at the number of blocks needed for
+       * the next GC, subtract this from the user's suggested heap size
+       * and use the rest for the allocation area.
+       */
+      blocks = (int)RtsFlags.GcFlags.heapSizeSuggestion - (int)needed;
+      
+      if (blocks < (int)RtsFlags.GcFlags.minAllocAreaSize) {
+       blocks = RtsFlags.GcFlags.minAllocAreaSize;
+      }
+      
+      resizeNursery((nat)blocks);
     }
   }
 
@@ -624,29 +654,9 @@ void GarbageCollect(void (*get_roots)(void))
   scheduleFinalisers(old_weak_ptr_list);
   
   /* check sanity after GC */
-#ifdef DEBUG
-  if (RtsFlags.GcFlags.generations == 1) {
-    IF_DEBUG(sanity, checkHeap(g0s0->to_space, NULL));
-    IF_DEBUG(sanity, checkChain(g0s0->large_objects));
-  } else {
-
-    for (g = 0; g <= N; g++) {
-      for (s = 0; s < generations[g].n_steps; s++) {
-       if (g == 0 && s == 0) { continue; }
-       IF_DEBUG(sanity, checkHeap(generations[g].steps[s].blocks, NULL));
-      }
-    }
-    for (g = N+1; g < RtsFlags.GcFlags.generations; g++) {
-      for (s = 0; s < generations[g].n_steps; s++) {
-       IF_DEBUG(sanity, checkHeap(generations[g].steps[s].blocks,
-                                  generations[g].steps[s].blocks->start));
-       IF_DEBUG(sanity, checkChain(generations[g].steps[s].large_objects));
-      }
-    }
-    IF_DEBUG(sanity, checkFreeListSanity());
-  }
-#endif
+  IF_DEBUG(sanity, checkSanity(N));
 
+  /* extra GC trace info */
   IF_DEBUG(gc, stat_describe_gens());
 
 #ifdef DEBUG
@@ -690,8 +700,7 @@ static rtsBool
 traverse_weak_ptr_list(void)
 {
   StgWeak *w, **last_w, *next_w;
-  StgClosure *target;
-  const StgInfoTable *info;
+  StgClosure *new;
   rtsBool flag = rtsFalse;
 
   if (weak_done) { return rtsFalse; }
@@ -703,56 +712,26 @@ traverse_weak_ptr_list(void)
 
   last_w = &old_weak_ptr_list;
   for (w = old_weak_ptr_list; w; w = next_w) {
-    target = w->key;
-  loop:
-    /* ignore weak pointers in older generations */
-    if (!LOOKS_LIKE_STATIC(target) && Bdescr((P_)target)->gen->no > N) {
-      IF_DEBUG(weak, fprintf(stderr,"Weak pointer still alive (in old gen) at %p\n", w));
-      /* remove this weak ptr from the old_weak_ptr list */
-      *last_w = w->link;
-      /* and put it on the new weak ptr list */
-      next_w  = w->link;
-      w->link = weak_ptr_list;
-      weak_ptr_list = w;
-      flag = rtsTrue;
-      continue;
-    }
-
-    info = get_itbl(target);
-    switch (info->type) {
-      
-    case IND:
-    case IND_STATIC:
-    case IND_PERM:
-    case IND_OLDGEN:           /* rely on compatible layout with StgInd */
-    case IND_OLDGEN_PERM:
-      /* follow indirections */
-      target = ((StgInd *)target)->indirectee;
-      goto loop;
 
-    case EVACUATED:
-      /* If key is alive, evacuate value and finaliser and 
-       * place weak ptr on new weak ptr list.
-       */
-      IF_DEBUG(weak, fprintf(stderr,"Weak pointer still alive at %p\n", w));
-      w->key = ((StgEvacuated *)target)->evacuee;
+    if ((new = isAlive(w->key))) {
+      w->key = new;
+      /* evacuate the value and finaliser */
       w->value = evacuate(w->value);
       w->finaliser = evacuate(w->finaliser);
-      
       /* remove this weak ptr from the old_weak_ptr list */
       *last_w = w->link;
-
       /* and put it on the new weak ptr list */
       next_w  = w->link;
       w->link = weak_ptr_list;
       weak_ptr_list = w;
       flag = rtsTrue;
-      break;
-
-    default:                   /* key is dead */
+      IF_DEBUG(weak, fprintf(stderr,"Weak pointer still alive at %p -> %p\n", w, w->key));
+      continue;
+    }
+    else {
       last_w = &(w->link);
       next_w = w->link;
-      break;
+      continue;
     }
   }
   
@@ -771,14 +750,60 @@ traverse_weak_ptr_list(void)
   return rtsTrue;
 }
 
+/* -----------------------------------------------------------------------------
+   isAlive determines whether the given closure is still alive (after
+   a garbage collection) or not.  It returns the new address of the
+   closure if it is alive, or NULL otherwise.
+   -------------------------------------------------------------------------- */
+
+StgClosure *
+isAlive(StgClosure *p)
+{
+  StgInfoTable *info;
+
+  while (1) {
+
+    info = get_itbl(p);
+
+    /* ToDo: for static closures, check the static link field.
+     * Problem here is that we sometimes don't set the link field, eg.
+     * for static closures with an empty SRT or CONSTR_STATIC_NOCAFs.
+     */
+
+    /* ignore closures in generations that we're not collecting. */
+    if (LOOKS_LIKE_STATIC(p) || Bdescr((P_)p)->gen->no > N) {
+      return p;
+    }
+    
+    switch (info->type) {
+      
+    case IND:
+    case IND_STATIC:
+    case IND_PERM:
+    case IND_OLDGEN:           /* rely on compatible layout with StgInd */
+    case IND_OLDGEN_PERM:
+      /* follow indirections */
+      p = ((StgInd *)p)->indirectee;
+      continue;
+      
+    case EVACUATED:
+      /* alive! */
+      return ((StgEvacuated *)p)->evacuee;
+
+    default:
+      /* dead. */
+      return NULL;
+    }
+  }
+}
+
 StgClosure *
 MarkRoot(StgClosure *root)
 {
-  root = evacuate(root);
-  return root;
+  return evacuate(root);
 }
 
-static inline void addBlock(step *step)
+static void addBlock(step *step)
 {
   bdescr *bd = allocBlock();
   bd->gen = step->gen;
@@ -799,17 +824,16 @@ static inline void addBlock(step *step)
 }
 
 static __inline__ StgClosure *
-copy(StgClosure *src, nat size, bdescr *bd)
+copy(StgClosure *src, nat size, step *step)
 {
-  step *step;
   P_ to, from, dest;
 
+  TICK_GC_WORDS_COPIED(size);
   /* Find out where we're going, using the handy "to" pointer in 
    * the step of the source object.  If it turns out we need to
    * evacuate to an older generation, adjust it here (see comment
    * by evacuate()).
    */
-  step = bd->step->to;
   if (step->gen->no < evac_gen) {
     step = &generations[evac_gen].steps[0];
   }
@@ -821,11 +845,12 @@ copy(StgClosure *src, nat size, bdescr *bd)
     addBlock(step);
   }
 
-  dest = step->hp;
-  step->hp += size;
-  for(to = dest, from = (P_)src; size>0; --size) {
+  for(to = step->hp, from = (P_)src; size>0; --size) {
     *to++ = *from++;
   }
+
+  dest = step->hp;
+  step->hp = to;
   return (StgClosure *)dest;
 }
 
@@ -835,12 +860,11 @@ copy(StgClosure *src, nat size, bdescr *bd)
  */
 
 static __inline__ StgClosure *
-copyPart(StgClosure *src, nat size_to_reserve, nat size_to_copy, bdescr *bd)
+copyPart(StgClosure *src, nat size_to_reserve, nat size_to_copy, step *step)
 {
-  step *step;
   P_ dest, to, from;
 
-  step = bd->step->to;
+  TICK_GC_WORDS_COPIED(size_to_copy);
   if (step->gen->no < evac_gen) {
     step = &generations[evac_gen].steps[0];
   }
@@ -849,12 +873,12 @@ copyPart(StgClosure *src, nat size_to_reserve, nat size_to_copy, bdescr *bd)
     addBlock(step);
   }
 
-  dest = step->hp;
-  step->hp += size_to_reserve;
-  for(to = dest, from = (P_)src; size_to_copy>0; --size_to_copy) {
+  for(to = step->hp, from = (P_)src; size_to_copy>0; --size_to_copy) {
     *to++ = *from++;
   }
   
+  dest = step->hp;
+  step->hp += size_to_reserve;
   return (StgClosure *)dest;
 }
 
@@ -868,25 +892,6 @@ upd_evacuee(StgClosure *p, StgClosure *dest)
 }
 
 /* -----------------------------------------------------------------------------
-   Evacuate a mutable object
-   
-   If we evacuate a mutable object to an old generation, cons the
-   object onto the older generation's mutable list.
-   -------------------------------------------------------------------------- */
-   
-static inline void
-evacuate_mutable(StgMutClosure *c)
-{
-  bdescr *bd;
-  
-  bd = Bdescr((P_)c);
-  if (bd->gen->no > 0) {
-    c->mut_link = bd->gen->mut_list;
-    bd->gen->mut_list = c;
-  }
-}
-
-/* -----------------------------------------------------------------------------
    Evacuate a large object
 
    This just consists of removing the object from the (doubly-linked)
@@ -913,6 +918,7 @@ evacuate_large(StgPtr p, rtsBool mutable)
      */
     if (bd->gen->no < evac_gen) {
       failed_to_evac = rtsTrue;
+      TICK_GC_FAILED_PROMOTION();
     }
     return;
   }
@@ -942,7 +948,7 @@ evacuate_large(StgPtr p, rtsBool mutable)
   bd->evacuated = 1;
 
   if (mutable) {
-    evacuate_mutable((StgMutClosure *)p);
+    recordMutable((StgMutClosure *)p);
   }
 }
 
@@ -974,7 +980,7 @@ mkMutCons(StgClosure *ptr, generation *gen)
 
   SET_HDR(q,&MUT_CONS_info,CCS_GC);
   q->var = ptr;
-  evacuate_mutable((StgMutClosure *)q);
+  recordOldToNewPtrs((StgMutClosure *)q);
 
   return (StgClosure *)q;
 }
@@ -1010,6 +1016,7 @@ evacuate(StgClosure *q)
 {
   StgClosure *to;
   bdescr *bd = NULL;
+  step *step;
   const StgInfoTable *info;
 
 loop:
@@ -1023,9 +1030,11 @@ loop:
       if (bd->gen->no < evac_gen) {
        /* nope */
        failed_to_evac = rtsTrue;
+       TICK_GC_FAILED_PROMOTION();
       }
       return q;
     }
+    step = bd->step->to;
   }
 
   /* make sure the info pointer is into text space */
@@ -1036,15 +1045,45 @@ loop:
   switch (info -> type) {
 
   case BCO:
-    to = copy(q,bco_sizeW(stgCast(StgBCO*,q)),bd);
+    to = copy(q,bco_sizeW(stgCast(StgBCO*,q)),step);
     upd_evacuee(q,to);
     return to;
 
   case MUT_VAR:
+    ASSERT(q->header.info != &MUT_CONS_info);
   case MVAR:
-    to = copy(q,sizeW_fromITBL(info),bd);
+    to = copy(q,sizeW_fromITBL(info),step);
+    upd_evacuee(q,to);
+    recordMutable((StgMutClosure *)to);
+    return to;
+
+  case STABLE_NAME:
+    stable_ptr_table[((StgStableName *)q)->sn].keep = rtsTrue;
+    to = copy(q,sizeofW(StgStableName),step);
+    upd_evacuee(q,to);
+    return to;
+
+  case FUN_1_0:
+  case FUN_0_1:
+  case CONSTR_1_0:
+  case CONSTR_0_1:
+    to = copy(q,sizeofW(StgHeader)+1,step);
+    upd_evacuee(q,to);
+    return to;
+
+  case THUNK_1_0:              /* here because of MIN_UPD_SIZE */
+  case THUNK_0_1:
+  case FUN_1_1:
+  case FUN_0_2:
+  case FUN_2_0:
+  case THUNK_1_1:
+  case THUNK_0_2:
+  case THUNK_2_0:
+  case CONSTR_1_1:
+  case CONSTR_0_2:
+  case CONSTR_2_0:
+    to = copy(q,sizeofW(StgHeader)+2,step);
     upd_evacuee(q,to);
-    evacuate_mutable((StgMutClosure *)to);
     return to;
 
   case FUN:
@@ -1056,20 +1095,20 @@ loop:
   case CAF_ENTERED:
   case WEAK:
   case FOREIGN:
-    to = copy(q,sizeW_fromITBL(info),bd);
+    to = copy(q,sizeW_fromITBL(info),step);
     upd_evacuee(q,to);
     return to;
 
   case CAF_BLACKHOLE:
   case BLACKHOLE:
-    to = copyPart(q,BLACKHOLE_sizeW(),sizeofW(StgHeader),bd);
+    to = copyPart(q,BLACKHOLE_sizeW(),sizeofW(StgHeader),step);
     upd_evacuee(q,to);
     return to;
 
   case BLACKHOLE_BQ:
-    to = copy(q,BLACKHOLE_sizeW(),bd); 
+    to = copy(q,BLACKHOLE_sizeW(),step); 
     upd_evacuee(q,to);
-    evacuate_mutable((StgMutClosure *)to);
+    recordMutable((StgMutClosure *)to);
     return to;
 
   case THUNK_SELECTOR:
@@ -1081,6 +1120,11 @@ loop:
       selectee_info = get_itbl(selectee);
       switch (selectee_info->type) {
       case CONSTR:
+      case CONSTR_1_0:
+      case CONSTR_0_1:
+      case CONSTR_2_0:
+      case CONSTR_1_1:
+      case CONSTR_0_2:
       case CONSTR_STATIC:
        { 
          StgNat32 offset = info->layout.selector_offset;
@@ -1102,6 +1146,7 @@ loop:
            if (bd->evacuated) {
              if (bd->gen->no < evac_gen) {
                failed_to_evac = rtsTrue;
+               TICK_GC_FAILED_PROMOTION();
              }
              return q;
            }
@@ -1130,6 +1175,11 @@ loop:
        goto selector_loop;
 
       case THUNK:
+      case THUNK_1_0:
+      case THUNK_0_1:
+      case THUNK_2_0:
+      case THUNK_1_1:
+      case THUNK_0_2:
       case THUNK_STATIC:
       case THUNK_SELECTOR:
        /* aargh - do recursively???? */
@@ -1144,7 +1194,7 @@ loop:
        barf("evacuate: THUNK_SELECTOR: strange selectee");
       }
     }
-    to = copy(q,THUNK_SELECTOR_sizeW(),bd);
+    to = copy(q,THUNK_SELECTOR_sizeW(),step);
     upd_evacuee(q,to);
     return to;
 
@@ -1204,7 +1254,7 @@ loop:
   case PAP:
     /* these are special - the payload is a copy of a chunk of stack,
        tagging and all. */
-    to = copy(q,pap_sizeW(stgCast(StgPAP*,q)),bd);
+    to = copy(q,pap_sizeW(stgCast(StgPAP*,q)),step);
     upd_evacuee(q,to);
     return to;
 
@@ -1221,11 +1271,11 @@ loop:
       if (Bdescr((P_)p)->gen->no < evac_gen) {
        /*      fprintf(stderr,"evac failed!\n");*/
        failed_to_evac = rtsTrue;
-      } 
+       TICK_GC_FAILED_PROMOTION();
+      }
     }
     return ((StgEvacuated*)q)->evacuee;
 
-  case MUT_ARR_WORDS:
   case ARR_WORDS:
     {
       nat size = arr_words_sizeW(stgCast(StgArrWords*,q)); 
@@ -1235,7 +1285,7 @@ loop:
        return q;
       } else {
        /* just copy the block */
-       to = copy(q,size,bd);
+       to = copy(q,size,step);
        upd_evacuee(q,to);
        return to;
       }
@@ -1251,10 +1301,10 @@ loop:
        to = q;
       } else {
        /* just copy the block */
-       to = copy(q,size,bd);
+       to = copy(q,size,step);
        upd_evacuee(q,to);
        if (info->type == MUT_ARR_PTRS) {
-         evacuate_mutable((StgMutClosure *)to);
+         recordMutable((StgMutClosure *)to);
        }
       }
       return to;
@@ -1276,7 +1326,7 @@ loop:
        * list it contains.  
        */
       } else {
-       StgTSO *new_tso = (StgTSO *)copy((StgClosure *)tso,tso_sizeW(tso),bd);
+       StgTSO *new_tso = (StgTSO *)copy((StgClosure *)tso,tso_sizeW(tso),step);
 
        diff = (StgPtr)new_tso - (StgPtr)tso; /* In *words* */
 
@@ -1288,7 +1338,7 @@ loop:
        relocate_TSO(tso, new_tso);
        upd_evacuee(q,(StgClosure *)new_tso);
 
-       evacuate_mutable((StgMutClosure *)new_tso);
+       recordMutable((StgMutClosure *)new_tso);
        return (StgClosure *)new_tso;
       }
     }
@@ -1447,6 +1497,54 @@ scavenge(step *step)
        break;
       }
 
+    case THUNK_2_0:
+    case FUN_2_0:
+      scavenge_srt(info);
+    case CONSTR_2_0:
+      ((StgClosure *)p)->payload[1] = evacuate(((StgClosure *)p)->payload[1]);
+      ((StgClosure *)p)->payload[0] = evacuate(((StgClosure *)p)->payload[0]);
+      p += sizeofW(StgHeader) + 2;
+      break;
+
+    case THUNK_1_0:
+      scavenge_srt(info);
+      ((StgClosure *)p)->payload[0] = evacuate(((StgClosure *)p)->payload[0]);
+      p += sizeofW(StgHeader) + 2; /* MIN_UPD_SIZE */
+      break;
+
+    case FUN_1_0:
+      scavenge_srt(info);
+    case CONSTR_1_0:
+      ((StgClosure *)p)->payload[0] = evacuate(((StgClosure *)p)->payload[0]);
+      p += sizeofW(StgHeader) + 1;
+      break;
+
+    case THUNK_0_1:
+      scavenge_srt(info);
+      p += sizeofW(StgHeader) + 2; /* MIN_UPD_SIZE */
+      break;
+
+    case FUN_0_1:
+      scavenge_srt(info);
+    case CONSTR_0_1:
+      p += sizeofW(StgHeader) + 1;
+      break;
+
+    case THUNK_0_2:
+    case FUN_0_2:
+      scavenge_srt(info);
+    case CONSTR_0_2:
+      p += sizeofW(StgHeader) + 2;
+      break;
+
+    case THUNK_1_1:
+    case FUN_1_1:
+      scavenge_srt(info);
+    case CONSTR_1_1:
+      ((StgClosure *)p)->payload[0] = evacuate(((StgClosure *)p)->payload[0]);
+      p += sizeofW(StgHeader) + 2;
+      break;
+
     case FUN:
     case THUNK:
       scavenge_srt(info);
@@ -1455,6 +1553,7 @@ scavenge(step *step)
     case CONSTR:
     case WEAK:
     case FOREIGN:
+    case STABLE_NAME:
     case IND_PERM:
     case IND_OLDGEN_PERM:
     case CAF_UNENTERED:
@@ -1492,7 +1591,7 @@ scavenge(step *step)
          evacuate((StgClosure *)bh->blocking_queue);
        if (failed_to_evac) {
          failed_to_evac = rtsFalse;
-         evacuate_mutable((StgMutClosure *)bh);
+         recordMutable((StgMutClosure *)bh);
        }
        p += BLACKHOLE_sizeW();
        break;
@@ -1548,7 +1647,6 @@ scavenge(step *step)
       }
       
     case ARR_WORDS:
-    case MUT_ARR_WORDS:
       /* nothing to follow */
       p += arr_words_sizeW(stgCast(StgArrWords*,p));
       break;
@@ -1578,7 +1676,7 @@ scavenge(step *step)
        }
        if (failed_to_evac) {
          /* we can do this easier... */
-         evacuate_mutable((StgMutClosure *)start);
+         recordMutable((StgMutClosure *)start);
          failed_to_evac = rtsFalse;
        }
        break;
@@ -1630,21 +1728,36 @@ scavenge(step *step)
    objects can have this property.
    -------------------------------------------------------------------------- */
 static rtsBool
-scavenge_one(StgPtr p)
+scavenge_one(StgClosure *p)
 {
   StgInfoTable *info;
   rtsBool no_luck;
 
-  ASSERT(p && (LOOKS_LIKE_GHC_INFO(GET_INFO((StgClosure *)p))
-              || IS_HUGS_CONSTR_INFO(GET_INFO((StgClosure *)p))));
+  ASSERT(p && (LOOKS_LIKE_GHC_INFO(GET_INFO(p))
+              || IS_HUGS_CONSTR_INFO(GET_INFO(p))));
 
-  info = get_itbl((StgClosure *)p);
+  info = get_itbl(p);
 
   switch (info -> type) {
 
   case FUN:
+  case FUN_1_0:                        /* hardly worth specialising these guys */
+  case FUN_0_1:
+  case FUN_1_1:
+  case FUN_0_2:
+  case FUN_2_0:
   case THUNK:
+  case THUNK_1_0:
+  case THUNK_0_1:
+  case THUNK_1_1:
+  case THUNK_0_2:
+  case THUNK_2_0:
   case CONSTR:
+  case CONSTR_1_0:
+  case CONSTR_0_1:
+  case CONSTR_1_1:
+  case CONSTR_0_2:
+  case CONSTR_2_0:
   case WEAK:
   case FOREIGN:
   case IND_PERM:
@@ -1652,11 +1765,11 @@ scavenge_one(StgPtr p)
   case CAF_UNENTERED:
   case CAF_ENTERED:
     {
-      StgPtr end;
+      StgPtr q, end;
       
-      end = (P_)((StgClosure *)p)->payload + info->layout.payload.ptrs;
-      for (p = (P_)((StgClosure *)p)->payload; p < end; p++) {
-       (StgClosure *)*p = evacuate((StgClosure *)*p);
+      end = (P_)p->payload + info->layout.payload.ptrs;
+      for (q = (P_)p->payload; q < end; q++) {
+       (StgClosure *)*q = evacuate((StgClosure *)*q);
       }
       break;
     }
@@ -1669,7 +1782,7 @@ scavenge_one(StgPtr p)
     { 
       StgSelector *s = (StgSelector *)p;
       s->selectee = evacuate(s->selectee);
-       break;
+      break;
     }
     
   case AP_UPD: /* same as PAPs */
@@ -1678,7 +1791,7 @@ scavenge_one(StgPtr p)
      * evacuate the function pointer too...
      */
     { 
-      StgPAP* pap = stgCast(StgPAP*,p);
+      StgPAP* pap = (StgPAP *)p;
       
       pap->fun = evacuate(pap->fun);
       scavenge_stack((P_)pap->payload, (P_)pap->payload + pap->n_args);
@@ -1711,21 +1824,122 @@ scavenge_one(StgPtr p)
    remove non-mutable objects from the mutable list at this point.
    -------------------------------------------------------------------------- */
 
-static StgMutClosure *
-scavenge_mutable_list(StgMutClosure *p, nat gen)
+static void
+scavenge_mut_once_list(generation *gen)
 {
   StgInfoTable *info;
-  StgMutClosure *start;
-  StgMutClosure **prev;
+  StgMutClosure *p, *next, *new_list;
 
-  evac_gen = 0;
+  p = gen->mut_once_list;
+  new_list = END_MUT_LIST;
+  next = p->mut_link;
+
+  evac_gen = gen->no;
+  failed_to_evac = rtsFalse;
 
-  prev = &start;
-  start = p;
+  for (; p != END_MUT_LIST; p = next, next = p->mut_link) {
 
+    /* make sure the info pointer is into text space */
+    ASSERT(p && (LOOKS_LIKE_GHC_INFO(GET_INFO(p))
+                || IS_HUGS_CONSTR_INFO(GET_INFO(p))));
+    
+    info = get_itbl(p);
+    switch(info->type) {
+      
+    case IND_OLDGEN:
+    case IND_OLDGEN_PERM:
+    case IND_STATIC:
+      /* Try to pull the indirectee into this generation, so we can
+       * remove the indirection from the mutable list.  
+       */
+      ((StgIndOldGen *)p)->indirectee = 
+        evacuate(((StgIndOldGen *)p)->indirectee);
+      
+#if 0  
+      /* Debugging code to print out the size of the thing we just
+       * promoted 
+       */
+      { 
+       StgPtr start = gen->steps[0].scan;
+       bdescr *start_bd = gen->steps[0].scan_bd;
+       nat size = 0;
+       scavenge(&gen->steps[0]);
+       if (start_bd != gen->steps[0].scan_bd) {
+         size += (P_)BLOCK_ROUND_UP(start) - start;
+         start_bd = start_bd->link;
+         while (start_bd != gen->steps[0].scan_bd) {
+           size += BLOCK_SIZE_W;
+           start_bd = start_bd->link;
+         }
+         size += gen->steps[0].scan -
+           (P_)BLOCK_ROUND_DOWN(gen->steps[0].scan);
+       } else {
+         size = gen->steps[0].scan - start;
+       }
+       fprintf(stderr,"evac IND_OLDGEN: %d bytes\n", size * sizeof(W_));
+      }
+#endif
+
+      /* failed_to_evac might happen if we've got more than two
+       * generations, we're collecting only generation 0, the
+       * indirection resides in generation 2 and the indirectee is
+       * in generation 1.
+       */
+      if (failed_to_evac) {
+       failed_to_evac = rtsFalse;
+       p->mut_link = new_list;
+       new_list = p;
+      } else {
+       /* the mut_link field of an IND_STATIC is overloaded as the
+        * static link field too (it just so happens that we don't need
+        * both at the same time), so we need to NULL it out when
+        * removing this object from the mutable list because the static
+        * link fields are all assumed to be NULL before doing a major
+        * collection. 
+        */
+       p->mut_link = NULL;
+      }
+      continue;
+      
+    case MUT_VAR:
+      /* MUT_CONS is a kind of MUT_VAR, except it that we try to remove
+       * it from the mutable list if possible by promoting whatever it
+       * points to.
+       */
+      ASSERT(p->header.info == &MUT_CONS_info);
+      if (scavenge_one(((StgMutVar *)p)->var) == rtsTrue) {
+       /* didn't manage to promote everything, so put the
+        * MUT_CONS back on the list.
+        */
+       p->mut_link = new_list;
+       new_list = p;
+      } 
+      continue;
+      
+    default:
+      /* shouldn't have anything else on the mutables list */
+      barf("scavenge_mut_once_list: strange object?");
+    }
+  }
+
+  gen->mut_once_list = new_list;
+}
+
+
+static void
+scavenge_mutable_list(generation *gen)
+{
+  StgInfoTable *info;
+  StgMutClosure *p, *next, *new_list;
+
+  p = gen->saved_mut_list;
+  new_list = END_MUT_LIST;
+  next = p->mut_link;
+
+  evac_gen = 0;
   failed_to_evac = rtsFalse;
 
-  for (; p != END_MUT_LIST; p = *prev) {
+  for (; p != END_MUT_LIST; p = next, next = p->mut_link) {
 
     /* make sure the info pointer is into text space */
     ASSERT(p && (LOOKS_LIKE_GHC_INFO(GET_INFO(p))
@@ -1742,7 +1956,7 @@ scavenge_mutable_list(StgMutClosure *p, nat gen)
        StgPtr end, q;
        
        end = (P_)p + mut_arr_ptrs_sizeW((StgMutArrPtrs*)p);
-       evac_gen = gen;
+       evac_gen = gen->no;
        for (q = (P_)((StgMutArrPtrs *)p)->payload; q < end; q++) {
          (StgClosure *)*q = evacuate((StgClosure *)*q);
        }
@@ -1750,16 +1964,16 @@ scavenge_mutable_list(StgMutClosure *p, nat gen)
 
        if (failed_to_evac) {
          failed_to_evac = rtsFalse;
-         prev = &p->mut_link;
-       } else {
-         *prev = p->mut_link;
-       }
+         p->mut_link = new_list;
+         new_list = p;
+       } 
        continue;
       }
 
     case MUT_ARR_PTRS:
       /* follow everything */
-      prev = &p->mut_link;
+      p->mut_link = new_list;
+      new_list = p;
       {
        StgPtr end, q;
        
@@ -1775,21 +1989,10 @@ scavenge_mutable_list(StgMutClosure *p, nat gen)
        * it from the mutable list if possible by promoting whatever it
        * points to.
        */
-      if (p->header.info == &MUT_CONS_info) {
-       evac_gen = gen;
-       if (scavenge_one((P_)((StgMutVar *)p)->var) == rtsTrue) {
-         /* didn't manage to promote everything, so leave the
-          * MUT_CONS on the list.
-          */
-         prev = &p->mut_link;
-       } else {
-         *prev = p->mut_link;
-       }
-       evac_gen = 0;
-      } else {
-       ((StgMutVar *)p)->var = evacuate(((StgMutVar *)p)->var);
-       prev = &p->mut_link;
-      }
+      ASSERT(p->header.info != &MUT_CONS_info);
+      ((StgMutVar *)p)->var = evacuate(((StgMutVar *)p)->var);
+      p->mut_link = new_list;
+      new_list = p;
       continue;
       
     case MVAR:
@@ -1798,7 +2001,8 @@ scavenge_mutable_list(StgMutClosure *p, nat gen)
        (StgClosure *)mvar->head = evacuate((StgClosure *)mvar->head);
        (StgClosure *)mvar->tail = evacuate((StgClosure *)mvar->tail);
        (StgClosure *)mvar->value = evacuate((StgClosure *)mvar->value);
-       prev = &p->mut_link;
+       p->mut_link = new_list;
+       new_list = p;
        continue;
       }
 
@@ -1821,52 +2025,28 @@ scavenge_mutable_list(StgMutClosure *p, nat gen)
         * point to some younger objects (because we set evac_gen to 0
         * above). 
         */
-       prev = &tso->mut_link;
+       tso->mut_link = new_list;
+       new_list = (StgMutClosure *)tso;
        continue;
       }
       
-    case IND_OLDGEN:
-    case IND_OLDGEN_PERM:
-    case IND_STATIC:
-      /* Try to pull the indirectee into this generation, so we can
-       * remove the indirection from the mutable list.  
-       */
-      evac_gen = gen;
-      ((StgIndOldGen *)p)->indirectee = 
-        evacuate(((StgIndOldGen *)p)->indirectee);
-      evac_gen = 0;
-
-      if (failed_to_evac) {
-       failed_to_evac = rtsFalse;
-       prev = &p->mut_link;
-      } else {
-       *prev = p->mut_link;
-       /* the mut_link field of an IND_STATIC is overloaded as the
-        * static link field too (it just so happens that we don't need
-        * both at the same time), so we need to NULL it out when
-        * removing this object from the mutable list because the static
-        * link fields are all assumed to be NULL before doing a major
-        * collection. 
-        */
-       p->mut_link = NULL;
-      }
-      continue;
-      
     case BLACKHOLE_BQ:
       { 
        StgBlockingQueue *bh = (StgBlockingQueue *)p;
        (StgClosure *)bh->blocking_queue = 
          evacuate((StgClosure *)bh->blocking_queue);
-       prev = &p->mut_link;
-       break;
+       p->mut_link = new_list;
+       new_list = p;
+       continue;
       }
 
     default:
       /* shouldn't have anything else on the mutables list */
-      barf("scavenge_mutable_object: non-mutable object?");
+      barf("scavenge_mut_list: strange object?");
     }
   }
-  return start;
+
+  gen->mut_list = new_list;
 }
 
 static void
@@ -1911,8 +2091,8 @@ scavenge_static(void)
        if (failed_to_evac) {
          failed_to_evac = rtsFalse;
          scavenged_static_objects = STATIC_LINK(info,p);
-         ((StgMutClosure *)ind)->mut_link = oldest_gen->mut_list;
-         oldest_gen->mut_list = (StgMutClosure *)ind;
+         ((StgMutClosure *)ind)->mut_link = oldest_gen->mut_once_list;
+         oldest_gen->mut_once_list = (StgMutClosure *)ind;
        }
        break;
       }
@@ -2030,25 +2210,27 @@ scavenge_stack(StgPtr p, StgPtr stack_end)
          continue;
        } else {
          bdescr *bd = Bdescr((P_)frame->updatee);
+         step *step;
          if (bd->gen->no > N) { 
            if (bd->gen->no < evac_gen) {
              failed_to_evac = rtsTrue;
            }
            continue;
          }
+         step = bd->step->to;
          switch (type) {
          case BLACKHOLE:
          case CAF_BLACKHOLE:
            to = copyPart(frame->updatee, BLACKHOLE_sizeW(), 
-                         sizeofW(StgHeader), bd);
+                         sizeofW(StgHeader), step);
            upd_evacuee(frame->updatee,to);
            frame->updatee = to;
            continue;
          case BLACKHOLE_BQ:
-           to = copy(frame->updatee, BLACKHOLE_sizeW(), bd);
+           to = copy(frame->updatee, BLACKHOLE_sizeW(), step);
            upd_evacuee(frame->updatee,to);
            frame->updatee = to;
-           evacuate_mutable((StgMutClosure *)to);
+           recordMutable((StgMutClosure *)to);
            continue;
          default:
            barf("scavenge_stack: UPDATE_FRAME updatee");
@@ -2155,7 +2337,6 @@ scavenge_large(step *step)
     /* only certain objects can be "large"... */
 
     case ARR_WORDS:
-    case MUT_ARR_WORDS:
       /* nothing to follow */
       continue;
 
@@ -2183,7 +2364,7 @@ scavenge_large(step *step)
        }
        evac_gen = 0;
        if (failed_to_evac) {
-         evacuate_mutable((StgMutClosure *)start);
+         recordMutable((StgMutClosure *)start);
        }
        continue;
       }
@@ -2379,15 +2560,16 @@ threadLazyBlackHole(StgTSO *tso)
       /* if the thunk is already blackholed, it means we've also
        * already blackholed the rest of the thunks on this stack,
        * so we can stop early.
+       *
+       * The blackhole made for a CAF is a CAF_BLACKHOLE, so they
+       * don't interfere with this optimisation.
        */
+      if (bh->header.info == &BLACKHOLE_info) {
+       return;
+      }
 
-      /* Don't for now: when we enter a CAF, we create a black hole on
-       * the heap and make the update frame point to it.  Thus the
-       * above optimisation doesn't apply.
-       */
-      if (bh->header.info != &BLACKHOLE_info
-         && bh->header.info != &BLACKHOLE_BQ_info
-         && bh->header.info != &CAF_BLACKHOLE_info) {
+      if (bh->header.info != &BLACKHOLE_BQ_info &&
+         bh->header.info != &CAF_BLACKHOLE_info) {
        SET_INFO(bh,&BLACKHOLE_info);
       }
 
@@ -2437,8 +2619,8 @@ threadSqueezeStack(StgTSO *tso)
    * added to the stack, rather than the way we see them in this
    * walk. (It makes the next loop less confusing.)  
    *
-   * Could stop if we find an update frame pointing to a black hole,
-   * but see comment in threadLazyBlackHole().
+   * Stop if we find an update frame pointing to a black hole 
+   * (see comment in threadLazyBlackHole()).
    */
   
   next_frame = NULL;
@@ -2447,6 +2629,10 @@ threadSqueezeStack(StgTSO *tso)
     frame->link = next_frame;
     next_frame = frame;
     frame = prev_frame;
+    if (get_itbl(frame)->type == UPDATE_FRAME
+       && frame->updatee->header.info == &BLACKHOLE_info) {
+        break;
+    }
   }
 
   /* Now, we're at the bottom.  Frame points to the lowest update
@@ -2544,10 +2730,8 @@ threadSqueezeStack(StgTSO *tso)
        */
       if (is_update_frame) {
        StgBlockingQueue *bh = (StgBlockingQueue *)frame->updatee;
-       if (bh->header.info != &BLACKHOLE_info
-           && bh->header.info != &BLACKHOLE_BQ_info
-           && bh->header.info != &CAF_BLACKHOLE_info
-           ) {
+       if (bh->header.info != &BLACKHOLE_BQ_info &&
+           bh->header.info != &CAF_BLACKHOLE_info) {
          SET_INFO(bh,&BLACKHOLE_info);
        }
       }