[project @ 1999-02-25 17:52:33 by simonm]
[ghc-hetmet.git] / ghc / rts / GC.c
index fa52dda..fad50be 100644 (file)
@@ -1,7 +1,9 @@
 /* -----------------------------------------------------------------------------
- * $Id: GC.c,v 1.20 1999/01/26 16:16:22 simonm Exp $
+ * $Id: GC.c,v 1.42 1999/02/25 17:52:33 simonm Exp $
  *
- * Two-space garbage collector
+ * (c) The GHC Team 1998-1999
+ *
+ * Generational garbage collector
  *
  * ---------------------------------------------------------------------------*/
 
@@ -89,6 +91,11 @@ static rtsBool failed_to_evac;
  */
 bdescr *old_to_space;
 
+/* Data used for allocation area sizing.
+ */
+lnat new_blocks;               /* blocks allocated during this GC */
+lnat g0s0_pcnt_kept = 30;      /* percentage of g0s0 live at last minor GC */
+
 /* -----------------------------------------------------------------------------
    Static function declarations
    -------------------------------------------------------------------------- */
@@ -103,7 +110,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);
@@ -136,7 +144,7 @@ void GarbageCollect(void (*get_roots)(void))
 {
   bdescr *bd;
   step *step;
-  lnat live, allocated, collected = 0;
+  lnat live, allocated, collected = 0, copied = 0;
   nat g, s;
 
 #ifdef PROFILING
@@ -191,7 +199,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
@@ -201,10 +209,16 @@ void GarbageCollect(void (*get_roots)(void))
     g0s0->to_space = NULL;
   }
 
+  /* Keep a count of how many new blocks we allocated during this GC
+   * (used for resizing the allocation area, later).
+   */
+  new_blocks = 0;
+
   /* Initialise to-space in all the generations/steps that we're
    * 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++) {
@@ -229,11 +243,12 @@ void GarbageCollect(void (*get_roots)(void))
       step->hpLim     = step->hp + BLOCK_SIZE_W;
       step->hp_bd     = bd;
       step->to_space  = bd;
-      step->to_blocks = 1; /* ???? */
+      step->to_blocks = 1;
       step->scan      = bd->start;
       step->scan_bd   = bd;
       step->new_large_objects = NULL;
       step->scavenged_large_objects = NULL;
+      new_blocks++;
       /* mark the large objects as not evacuated yet */
       for (bd = step->large_objects; bd; bd = bd->link) {
        bd->evacuated = 0;
@@ -258,6 +273,7 @@ void GarbageCollect(void (*get_roots)(void))
        step->hp_bd = bd;
        step->blocks = bd;
        step->n_blocks = 1;
+       new_blocks++;
       }
       /* Set the scan pointer for older generations: remember we
        * still have to scavenge objects that have been promoted. */
@@ -283,19 +299,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;
     }
   }
 
@@ -373,18 +397,24 @@ 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--) {
+         if (gen == 0 && st == 0 && RtsFlags.GcFlags.generations > 1) { 
+           continue; 
+         }
+         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;
          }
        }
       }
@@ -424,67 +454,11 @@ void GarbageCollect(void (*get_roots)(void))
        }
       }
     }
-  } 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 * 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;
-      }
-    }
-    
-    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 
    */
+  copied = new_blocks * BLOCK_SIZE_W;
   for (g = 0; g < RtsFlags.GcFlags.generations; g++) {
 
     if (g <= N) {
@@ -499,6 +473,11 @@ void GarbageCollect(void (*get_roots)(void))
        /* Tidy the end of the to-space chains */
        step->hp_bd->free = step->hp;
        step->hp_bd->link = NULL;
+       /* stats information: how much we copied */
+       if (g <= N) {
+         copied -= step->hp_bd->start + BLOCK_SIZE_W -
+           step->hp_bd->free;
+       }
       }
 
       /* for generations we collected... */
@@ -540,16 +519,16 @@ 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));
+#if 0
+         generations[g].max_blocks = (oldest_gen->max_blocks * g)
+              / (RtsFlags.GcFlags.generations-1);
+#endif
+         generations[g].max_blocks = oldest_gen->max_blocks;
        }
 
       /* for older generations... */
@@ -571,34 +550,112 @@ void GarbageCollect(void (*get_roots)(void))
     }
   }
   
+  /* Guess the amount of live data for stats. */
+  live = calcLive();
+
+  /* Free the small objects allocated via allocate(), since this will
+   * 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;
+  alloc_blocks_lim = RtsFlags.GcFlags.minAllocAreaSize;
+
   /* 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);
     }
     for (bd = g0s0->to_space; bd != NULL; bd = bd->link) {
       bd->evacuated = 0;       /* now from-space */
     }
-    live = g0s0->to_blocks * BLOCK_SIZE_W + 
-      ((lnat)g0s0->hp_bd->free - (lnat)g0s0->hp_bd->start) / sizeof(W_);
 
-  /* 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->to_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 obtained by finding the
+       * percentage of g0s0 that was live at the last minor GC.
+       */
+      if (N == 0) {
+       g0s0_pcnt_kept = (new_blocks * 100) / g0s0->n_blocks;
+      }
+
+      /* Estimate a size for the allocation area based on the
+       * information available.  We might end up going slightly under
+       * or over the suggested heap size, but we should be pretty
+       * close on average.
+       *
+       * Formula:            suggested - needed
+       *                ----------------------------
+       *                    1 + g0s0_pcnt_kept/100
+       *
+       * where 'needed' is the amount of memory needed at the next
+       * collection for collecting all steps except g0s0.
+       */
+      blocks = 
+       (((int)RtsFlags.GcFlags.heapSizeSuggestion - (int)needed) * 100) /
+       (100 + (int)g0s0_pcnt_kept);
+      
+      if (blocks < (int)RtsFlags.GcFlags.minAllocAreaSize) {
+       blocks = RtsFlags.GcFlags.minAllocAreaSize;
       }
+      
+      resizeNursery((nat)blocks);
     }
   }
 
@@ -621,46 +678,17 @@ void GarbageCollect(void (*get_roots)(void))
     bd->free = bd->start;
     ASSERT(bd->gen == g0);
     ASSERT(bd->step == g0s0);
+    IF_DEBUG(sanity,memset(bd->start, 0xaa, BLOCK_SIZE));
   }
   current_nursery = g0s0->blocks;
 
-  /* Free the small objects allocated via allocate(), since this will
-   * all have been copied into G0S1 now.  
-   */
-  if (small_alloc_list != NULL) {
-    freeChain(small_alloc_list);
-  }
-  small_alloc_list = NULL;
-  alloc_blocks = 0;
-  alloc_blocks_lim = RtsFlags.GcFlags.minAllocAreaSize;
-
-  /* start any pending finalisers */
-  scheduleFinalisers(old_weak_ptr_list);
+  /* start any pending finalizers */
+  scheduleFinalizers(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
@@ -677,7 +705,7 @@ void GarbageCollect(void (*get_roots)(void))
   IF_DEBUG(sanity, memInventory());
 
   /* ok, GC over: tell the stats department what happened. */
-  stat_endGC(allocated, collected, live, N);
+  stat_endGC(allocated, collected, live, copied, N);
 }
 
 /* -----------------------------------------------------------------------------
@@ -694,7 +722,7 @@ void GarbageCollect(void (*get_roots)(void))
    new live weak pointers, then all the currently unreachable ones are
    dead.
 
-   For generational GC: we just don't try to finalise weak pointers in
+   For generational GC: we just don't try to finalize weak pointers in
    older generations than the one we're collecting.  This could
    probably be optimised by keeping per-generation lists of weak
    pointers, but for a few weak pointers this scheme will work.
@@ -709,7 +737,7 @@ traverse_weak_ptr_list(void)
 
   if (weak_done) { return rtsFalse; }
 
-  /* doesn't matter where we evacuate values/finalisers to, since
+  /* doesn't matter where we evacuate values/finalizers to, since
    * these pointers are treated as roots (iff the keys are alive).
    */
   evac_gen = 0;
@@ -719,9 +747,9 @@ traverse_weak_ptr_list(void)
 
     if ((new = isAlive(w->key))) {
       w->key = new;
-      /* evacuate the value and finaliser */
+      /* evacuate the value and finalizer */
       w->value = evacuate(w->value);
-      w->finaliser = evacuate(w->finaliser);
+      w->finalizer = evacuate(w->finalizer);
       /* remove this weak ptr from the old_weak_ptr list */
       *last_w = w->link;
       /* and put it on the new weak ptr list */
@@ -741,12 +769,12 @@ traverse_weak_ptr_list(void)
   
   /* If we didn't make any changes, then we can go round and kill all
    * the dead weak pointers.  The old_weak_ptr list is used as a list
-   * of pending finalisers later on.
+   * of pending finalizers later on.
    */
   if (flag == rtsFalse) {
     for (w = old_weak_ptr_list; w; w = w->link) {
       w->value = evacuate(w->value);
-      w->finaliser = evacuate(w->finaliser);
+      w->finalizer = evacuate(w->finalizer);
     }
     weak_done = rtsTrue;
   }
@@ -825,6 +853,14 @@ static void addBlock(step *step)
   step->hpLim = step->hp + BLOCK_SIZE_W;
   step->hp_bd = bd;
   step->to_blocks++;
+  new_blocks++;
+}
+
+static __inline__ void 
+upd_evacuee(StgClosure *p, StgClosure *dest)
+{
+  p->header.info = &EVACUATED_info;
+  ((StgEvacuated *)p)->evacuee = dest;
 }
 
 static __inline__ StgClosure *
@@ -832,13 +868,18 @@ copy(StgClosure *src, nat size, 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()).
    */
   if (step->gen->no < evac_gen) {
+#ifdef NO_EAGER_PROMOTION    
+    failed_to_evac = rtsTrue;
+#else
     step = &generations[evac_gen].steps[0];
+#endif
   }
 
   /* chain a new block onto the to-space for the destination step if
@@ -854,6 +895,7 @@ copy(StgClosure *src, nat size, step *step)
 
   dest = step->hp;
   step->hp = to;
+  upd_evacuee(src,(StgClosure *)dest);
   return (StgClosure *)dest;
 }
 
@@ -867,8 +909,13 @@ copyPart(StgClosure *src, nat size_to_reserve, nat size_to_copy, step *step)
 {
   P_ dest, to, from;
 
+  TICK_GC_WORDS_COPIED(size_to_copy);
   if (step->gen->no < evac_gen) {
+#ifdef NO_EAGER_PROMOTION    
+    failed_to_evac = rtsTrue;
+#else
     step = &generations[evac_gen].steps[0];
+#endif
   }
 
   if (step->hp + size_to_reserve >= step->hpLim) {
@@ -881,37 +928,10 @@ copyPart(StgClosure *src, nat size_to_reserve, nat size_to_copy, step *step)
   
   dest = step->hp;
   step->hp += size_to_reserve;
+  upd_evacuee(src,(StgClosure *)dest);
   return (StgClosure *)dest;
 }
 
-static __inline__ void 
-upd_evacuee(StgClosure *p, StgClosure *dest)
-{
-  StgEvacuated *q = (StgEvacuated *)p;
-
-  SET_INFO(q,&EVACUATED_info);
-  q->evacuee = 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
 
@@ -959,7 +979,11 @@ evacuate_large(StgPtr p, rtsBool mutable)
    */
   step = bd->step->to;
   if (step->gen->no < evac_gen) {
+#ifdef NO_EAGER_PROMOTION    
+    failed_to_evac = rtsTrue;
+#else
     step = &generations[evac_gen].steps[0];
+#endif
   }
 
   bd->step = step;
@@ -969,7 +993,7 @@ evacuate_large(StgPtr p, rtsBool mutable)
   bd->evacuated = 1;
 
   if (mutable) {
-    evacuate_mutable((StgMutClosure *)p);
+    recordMutable((StgMutClosure *)p);
   }
 }
 
@@ -1001,7 +1025,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;
 }
@@ -1066,45 +1090,46 @@ loop:
   switch (info -> type) {
 
   case BCO:
-    to = copy(q,bco_sizeW(stgCast(StgBCO*,q)),step);
-    upd_evacuee(q,to);
-    return to;
+    return copy(q,bco_sizeW(stgCast(StgBCO*,q)),step);
 
   case MUT_VAR:
+    ASSERT(q->header.info != &MUT_CONS_info);
   case MVAR:
     to = copy(q,sizeW_fromITBL(info),step);
-    upd_evacuee(q,to);
-    evacuate_mutable((StgMutClosure *)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;
+    return copy(q,sizeofW(StgStableName),step);
 
   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;
+    return copy(q,sizeofW(StgHeader)+1,step);
 
   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:
+#ifdef NO_PROMOTE_THUNKS
+    if (bd->gen->no == 0 && 
+       bd->step->no != 0 &&
+       bd->step->no == bd->gen->n_steps-1) {
+      step = bd->step;
+    }
+#endif
+    return copy(q,sizeofW(StgHeader)+2,step);
+
+  case FUN_1_1:
+  case FUN_0_2:
+  case FUN_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);
-    return to;
+    return copy(q,sizeofW(StgHeader)+2,step);
 
   case FUN:
   case THUNK:
@@ -1115,20 +1140,15 @@ loop:
   case CAF_ENTERED:
   case WEAK:
   case FOREIGN:
-    to = copy(q,sizeW_fromITBL(info),step);
-    upd_evacuee(q,to);
-    return to;
+    return copy(q,sizeW_fromITBL(info),step);
 
   case CAF_BLACKHOLE:
   case BLACKHOLE:
-    to = copyPart(q,BLACKHOLE_sizeW(),sizeofW(StgHeader),step);
-    upd_evacuee(q,to);
-    return to;
+    return copyPart(q,BLACKHOLE_sizeW(),sizeofW(StgHeader),step);
 
   case BLACKHOLE_BQ:
     to = copy(q,BLACKHOLE_sizeW(),step); 
-    upd_evacuee(q,to);
-    evacuate_mutable((StgMutClosure *)to);
+    recordMutable((StgMutClosure *)to);
     return to;
 
   case THUNK_SELECTOR:
@@ -1214,9 +1234,7 @@ loop:
        barf("evacuate: THUNK_SELECTOR: strange selectee");
       }
     }
-    to = copy(q,THUNK_SELECTOR_sizeW(),step);
-    upd_evacuee(q,to);
-    return to;
+    return copy(q,THUNK_SELECTOR_sizeW(),step);
 
   case IND:
   case IND_OLDGEN:
@@ -1274,9 +1292,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)),step);
-    upd_evacuee(q,to);
-    return to;
+    return copy(q,pap_sizeW(stgCast(StgPAP*,q)),step);
 
   case EVACUATED:
     /* Already evacuated, just return the forwarding address.
@@ -1292,11 +1308,10 @@ loop:
        /*      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)); 
@@ -1306,9 +1321,7 @@ loop:
        return q;
       } else {
        /* just copy the block */
-       to = copy(q,size,step);
-       upd_evacuee(q,to);
-       return to;
+       return copy(q,size,step);
       }
     }
 
@@ -1323,9 +1336,8 @@ loop:
       } else {
        /* just copy the block */
        to = copy(q,size,step);
-       upd_evacuee(q,to);
        if (info->type == MUT_ARR_PTRS) {
-         evacuate_mutable((StgMutClosure *)to);
+         recordMutable((StgMutClosure *)to);
        }
       }
       return to;
@@ -1357,9 +1369,8 @@ loop:
        new_tso->splim = (StgPtr)new_tso->splim + diff;
        
        relocate_TSO(tso, new_tso);
-       upd_evacuee(q,(StgClosure *)new_tso);
 
-       evacuate_mutable((StgMutClosure *)new_tso);
+       recordMutable((StgMutClosure *)new_tso);
        return (StgClosure *)new_tso;
       }
     }
@@ -1612,7 +1623,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;
@@ -1668,7 +1679,6 @@ scavenge(step *step)
       }
       
     case ARR_WORDS:
-    case MUT_ARR_WORDS:
       /* nothing to follow */
       p += arr_words_sizeW(stgCast(StgArrWords*,p));
       break;
@@ -1698,7 +1708,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;
@@ -1750,15 +1760,15 @@ 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) {
 
@@ -1787,11 +1797,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;
     }
@@ -1804,7 +1814,7 @@ scavenge_one(StgPtr p)
     { 
       StgSelector *s = (StgSelector *)p;
       s->selectee = evacuate(s->selectee);
-       break;
+      break;
     }
     
   case AP_UPD: /* same as PAPs */
@@ -1813,7 +1823,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);
@@ -1846,21 +1856,121 @@ 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;
+
+  for (; p != END_MUT_LIST; p = next, next = p->mut_link) {
 
-  prev = &start;
-  start = p;
+    /* 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;
+
+  p = gen->saved_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))
@@ -1877,7 +1987,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);
        }
@@ -1885,16 +1995,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 = gen->mut_list;
+         gen->mut_list = p;
+       } 
        continue;
       }
 
     case MUT_ARR_PTRS:
       /* follow everything */
-      prev = &p->mut_link;
+      p->mut_link = gen->mut_list;
+      gen->mut_list = p;
       {
        StgPtr end, q;
        
@@ -1910,21 +2020,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 = gen->mut_list;
+      gen->mut_list = p;
       continue;
       
     case MVAR:
@@ -1933,7 +2032,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 = gen->mut_list;
+       gen->mut_list = p;
        continue;
       }
 
@@ -1956,52 +2056,26 @@ 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 = gen->mut_list;
+       gen->mut_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 = gen->mut_list;
+       gen->mut_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;
 }
 
 static void
@@ -2046,8 +2120,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;
       }
@@ -2172,20 +2246,26 @@ scavenge_stack(StgPtr p, StgPtr stack_end)
            }
            continue;
          }
-         step = bd->step->to;
+
+         /* Don't promote blackholes */
+         step = bd->step;
+         if (!(step->gen->no == 0 && 
+               step->no != 0 &&
+               step->no == step->gen->n_steps-1)) {
+           step = step->to;
+         }
+
          switch (type) {
          case BLACKHOLE:
          case CAF_BLACKHOLE:
            to = copyPart(frame->updatee, BLACKHOLE_sizeW(), 
                          sizeofW(StgHeader), step);
-           upd_evacuee(frame->updatee,to);
            frame->updatee = to;
            continue;
          case BLACKHOLE_BQ:
            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");
@@ -2292,7 +2372,6 @@ scavenge_large(step *step)
     /* only certain objects can be "large"... */
 
     case ARR_WORDS:
-    case MUT_ARR_WORDS:
       /* nothing to follow */
       continue;
 
@@ -2320,7 +2399,7 @@ scavenge_large(step *step)
        }
        evac_gen = 0;
        if (failed_to_evac) {
-         evacuate_mutable((StgMutClosure *)start);
+         recordMutable((StgMutClosure *)start);
        }
        continue;
       }