mkIndStaticInfoLabel,
         mkMainCapabilityLabel,
        mkMAP_FROZEN_infoLabel,
+       mkMAP_DIRTY_infoLabel,
         mkEMPTY_MVAR_infoLabel,
 
        mkTopTickyCtrLabel,
 mkIndStaticInfoLabel           = RtsLabel (RtsInfo SLIT("stg_IND_STATIC"))
 mkMainCapabilityLabel          = RtsLabel (RtsData SLIT("MainCapability"))
 mkMAP_FROZEN_infoLabel         = RtsLabel (RtsInfo SLIT("stg_MUT_ARR_PTRS_FROZEN0"))
+mkMAP_DIRTY_infoLabel          = RtsLabel (RtsInfo SLIT("stg_MUT_ARR_PTRS_DIRTY"))
 mkEMPTY_MVAR_infoLabel         = RtsLabel (RtsInfo SLIT("stg_EMPTY_MVAR"))
 
 mkTopTickyCtrLabel             = RtsLabel (RtsData SLIT("top_ct"))
 
 import CgInfoTbls      ( getConstrTag )
 import CgUtils         ( cmmOffsetW, cmmOffsetB, cmmLoadIndexW )
 import Cmm
-import CLabel          ( mkMAP_FROZEN_infoLabel )
+import CLabel          ( mkMAP_FROZEN_infoLabel, mkMAP_DIRTY_infoLabel )
 import CmmUtils
 import MachOp
 import SMRep
    = panic "CgPrimOp: doWriteByteArrayOp"
 
 doWritePtrArrayOp addr idx val
-   = mkBasicIndexedWrite arrPtrsHdrSize Nothing wordRep addr idx val
+   = do stmtC (setInfo addr (CmmLit (CmmLabel mkMAP_DIRTY_infoLabel)))
+        mkBasicIndexedWrite arrPtrsHdrSize Nothing wordRep addr idx val
 
 
 mkBasicIndexedRead off Nothing read_rep res base idx
 
 #define SE_CAF_BLACKHOLE       48
 #define MVAR                   49
 #define ARR_WORDS              50
-#define MUT_ARR_PTRS           51
-#define MUT_ARR_PTRS_FROZEN0    52
-#define MUT_ARR_PTRS_FROZEN     53
-#define MUT_VAR                        54
-#define WEAK                   55
-#define STABLE_NAME            56
-#define TSO                    57
-#define BLOCKED_FETCH          58
-#define FETCH_ME                59
-#define FETCH_ME_BQ             60
-#define RBH                     61
-#define EVACUATED               62
-#define REMOTE_REF              63
-#define TVAR_WAIT_QUEUE         64
-#define TVAR                    65
-#define TREC_CHUNK              66
-#define TREC_HEADER             67
-#define ATOMICALLY_FRAME        68
-#define CATCH_RETRY_FRAME       69
-#define CATCH_STM_FRAME         70
-#define N_CLOSURE_TYPES         71
+#define MUT_ARR_PTRS_CLEAN      51
+#define MUT_ARR_PTRS_DIRTY      52
+#define MUT_ARR_PTRS_FROZEN0    53
+#define MUT_ARR_PTRS_FROZEN     54
+#define MUT_VAR                        55
+#define WEAK                   56
+#define STABLE_NAME            57
+#define TSO                    58
+#define BLOCKED_FETCH          59
+#define FETCH_ME                60
+#define FETCH_ME_BQ             61
+#define RBH                     62
+#define EVACUATED               63
+#define REMOTE_REF              64
+#define TVAR_WAIT_QUEUE         65
+#define TVAR                    66
+#define TREC_CHUNK              67
+#define TREC_HEADER             68
+#define ATOMICALLY_FRAME        69
+#define CATCH_RETRY_FRAME       70
+#define CATCH_STM_FRAME         71
+#define N_CLOSURE_TYPES         72
 
 #endif /* CLOSURETYPES_H */
 
 RTS_INFO(stg_TSO_info);
 RTS_INFO(stg_ARR_WORDS_info);
 RTS_INFO(stg_MUT_ARR_WORDS_info);
-RTS_INFO(stg_MUT_ARR_PTRS_info);
+RTS_INFO(stg_MUT_ARR_PTRS_CLEAN_info);
+RTS_INFO(stg_MUT_ARR_PTRS_DIRTY_info);
 RTS_INFO(stg_MUT_ARR_PTRS_FROZEN_info);
 RTS_INFO(stg_MUT_ARR_PTRS_FROZEN0_info);
 RTS_INFO(stg_MUT_VAR_info);
 RTS_ENTRY(stg_TSO_entry);
 RTS_ENTRY(stg_ARR_WORDS_entry);
 RTS_ENTRY(stg_MUT_ARR_WORDS_entry);
-RTS_ENTRY(stg_MUT_ARR_PTRS_entry);
+RTS_ENTRY(stg_MUT_ARR_PTRS_CLEAN_entry);
+RTS_ENTRY(stg_MUT_ARR_PTRS_DIRTY_entry);
 RTS_ENTRY(stg_MUT_ARR_PTRS_FROZEN_entry);
 RTS_ENTRY(stg_MUT_ARR_PTRS_FROZEN0_entry);
 RTS_ENTRY(stg_MUT_VAR_entry);
 
 /* SE_CAF_BLACKHOLE    = */ (          _NS|              _UPT           ),
 /* MVAR                        = */ (_HNF|     _NS|         _MUT|_UPT           ),
 /* ARR_WORDS           = */ (_HNF|     _NS|              _UPT           ),
-/* MUT_ARR_PTRS                = */ (_HNF|     _NS|         _MUT|_UPT           ),
+/* MUT_ARR_PTRS_CLEAN          = */ (_HNF|     _NS|         _MUT|_UPT           ),
+/* MUT_ARR_PTRS_DIRTY          = */ (_HNF|     _NS|         _MUT|_UPT           ),
 /* MUT_ARR_PTRS_FROZEN0        = */ (_HNF|     _NS|         _MUT|_UPT           ),
 /* MUT_ARR_PTRS_FROZEN         = */ (_HNF|     _NS|              _UPT           ),
 /* MUT_VAR             = */ (_HNF|     _NS|         _MUT|_UPT           ),
 /* CATCH_STM_FRAME      = */ (     _BTM                                  )
 };
 
-#if N_CLOSURE_TYPES != 71
+#if N_CLOSURE_TYPES != 72
 #error Closure types changed: update ClosureFlags.c!
 #endif
 
 
  */
 static nat evac_gen;
 
+/* Whether to do eager promotion or not.
+ */
+static rtsBool eager_promotion;
+
 /* Weak pointers
  */
 StgWeak *old_weak_ptr_list; // also pending finaliser list
       mark_stack_bdescr = NULL;
   }
 
+  eager_promotion = rtsTrue; // for now
+
   /* -----------------------------------------------------------------------
    * follow all the roots that we know about:
    *   - mutable lists from each generation > N
    * by evacuate()).
    */
   if (stp->gen_no < evac_gen) {
-#ifdef NO_EAGER_PROMOTION    
-    failed_to_evac = rtsTrue;
-#else
-    stp = &generations[evac_gen].steps[0];
-#endif
+      if (eager_promotion) {
+         stp = &generations[evac_gen].steps[0];
+      } else {
+         failed_to_evac = rtsTrue;
+      }
   }
 
   /* chain a new block onto the to-space for the destination step if
    * by evacuate()).
    */
   if (stp->gen_no < evac_gen) {
-#ifdef NO_EAGER_PROMOTION    
-    failed_to_evac = rtsTrue;
-#else
-    stp = &generations[evac_gen].steps[0];
-#endif
+      if (eager_promotion) {
+         stp = &generations[evac_gen].steps[0];
+      } else {
+         failed_to_evac = rtsTrue;
+      }
   }
 
   /* chain a new block onto the to-space for the destination step if
 
   TICK_GC_WORDS_COPIED(size_to_copy);
   if (stp->gen_no < evac_gen) {
-#ifdef NO_EAGER_PROMOTION    
-    failed_to_evac = rtsTrue;
-#else
-    stp = &generations[evac_gen].steps[0];
-#endif
+      if (eager_promotion) {
+         stp = &generations[evac_gen].steps[0];
+      } else {
+         failed_to_evac = rtsTrue;
+      }
   }
 
   if (stp->hp + size_to_reserve >= stp->hpLim) {
    */
   stp = bd->step->to;
   if (stp->gen_no < evac_gen) {
-#ifdef NO_EAGER_PROMOTION    
-    failed_to_evac = rtsTrue;
-#else
-    stp = &generations[evac_gen].steps[0];
-#endif
+      if (eager_promotion) {
+         stp = &generations[evac_gen].steps[0];
+      } else {
+         failed_to_evac = rtsTrue;
+      }
   }
 
   bd->step = stp;
       // just copy the block 
       return copy_noscav(q,arr_words_sizeW((StgArrWords *)q),stp);
 
-  case MUT_ARR_PTRS:
+  case MUT_ARR_PTRS_CLEAN:
+  case MUT_ARR_PTRS_DIRTY:
   case MUT_ARR_PTRS_FROZEN:
   case MUT_ARR_PTRS_FROZEN0:
       // just copy the block 
        p += arr_words_sizeW((StgArrWords *)p);
        break;
 
-    case MUT_ARR_PTRS:
+    case MUT_ARR_PTRS_CLEAN:
+    case MUT_ARR_PTRS_DIRTY:
        // follow everything 
     {
        StgPtr next;
-
-       evac_gen = 0;           // repeatedly mutable 
+       rtsBool saved_eager;
+
+       // We don't eagerly promote objects pointed to by a mutable
+       // array, but if we find the array only points to objects in
+       // the same or an older generation, we mark it "clean" and
+       // avoid traversing it during minor GCs.
+       saved_eager = eager_promotion;
+       eager_promotion = rtsFalse;
        next = p + mut_arr_ptrs_sizeW((StgMutArrPtrs*)p);
        for (p = (P_)((StgMutArrPtrs *)p)->payload; p < next; p++) {
            *p = (StgWord)(StgPtr)evacuate((StgClosure *)*p);
        }
-       evac_gen = saved_evac_gen;
-       failed_to_evac = rtsTrue; // mutable anyhow.
+       eager_promotion = saved_eager;
+
+       if (failed_to_evac) {
+           ((StgClosure *)q)->header.info = &stg_MUT_ARR_PTRS_DIRTY_info;
+       } else {
+           ((StgClosure *)q)->header.info = &stg_MUT_ARR_PTRS_CLEAN_info;
+       }
+
+       failed_to_evac = rtsTrue; // always put it on the mutable list.
        break;
     }
 
            scavenge_AP((StgAP *)p);
            break;
       
-       case MUT_ARR_PTRS:
+       case MUT_ARR_PTRS_CLEAN:
+       case MUT_ARR_PTRS_DIRTY:
            // follow everything 
        {
            StgPtr next;
-           
-           evac_gen = 0;               // repeatedly mutable 
+           rtsBool saved_eager;
+
+           // We don't eagerly promote objects pointed to by a mutable
+           // array, but if we find the array only points to objects in
+           // the same or an older generation, we mark it "clean" and
+           // avoid traversing it during minor GCs.
+           saved_eager = eager_promotion;
+           eager_promotion = rtsFalse;
            next = p + mut_arr_ptrs_sizeW((StgMutArrPtrs*)p);
            for (p = (P_)((StgMutArrPtrs *)p)->payload; p < next; p++) {
                *p = (StgWord)(StgPtr)evacuate((StgClosure *)*p);
            }
-           evac_gen = saved_evac_gen;
+           eager_promotion = saved_eager;
+
+           if (failed_to_evac) {
+               ((StgClosure *)q)->header.info = &stg_MUT_ARR_PTRS_DIRTY_info;
+           } else {
+               ((StgClosure *)q)->header.info = &stg_MUT_ARR_PTRS_CLEAN_info;
+           }
+
            failed_to_evac = rtsTrue; // mutable anyhow.
            break;
        }
        // nothing to follow 
        break;
 
-    case MUT_ARR_PTRS:
+    case MUT_ARR_PTRS_CLEAN:
+    case MUT_ARR_PTRS_DIRTY:
     {
-       // follow everything 
-       StgPtr next;
-      
-       evac_gen = 0;           // repeatedly mutable 
+       StgPtr next, q;
+       rtsBool saved_eager;
+
+       // We don't eagerly promote objects pointed to by a mutable
+       // array, but if we find the array only points to objects in
+       // the same or an older generation, we mark it "clean" and
+       // avoid traversing it during minor GCs.
+       saved_eager = eager_promotion;
+       eager_promotion = rtsFalse;
+       q = p;
        next = p + mut_arr_ptrs_sizeW((StgMutArrPtrs*)p);
        for (p = (P_)((StgMutArrPtrs *)p)->payload; p < next; p++) {
            *p = (StgWord)(StgPtr)evacuate((StgClosure *)*p);
        }
-       evac_gen = saved_evac_gen;
+       eager_promotion = saved_eager;
+
+       if (failed_to_evac) {
+           ((StgClosure *)q)->header.info = &stg_MUT_ARR_PTRS_DIRTY_info;
+       } else {
+           ((StgClosure *)q)->header.info = &stg_MUT_ARR_PTRS_CLEAN_info;
+       }
+
        failed_to_evac = rtsTrue;
        break;
     }
            switch (get_itbl((StgClosure *)p)->type) {
            case MUT_VAR:
                mutlist_MUTVARS++; break;
-           case MUT_ARR_PTRS:
+           case MUT_ARR_PTRS_CLEAN:
+           case MUT_ARR_PTRS_DIRTY:
            case MUT_ARR_PTRS_FROZEN:
            case MUT_ARR_PTRS_FROZEN0:
                mutlist_MUTARRS++; break;
            }
 #endif
 
+           // We don't need to scavenge clean arrays.  This is the
+           // Whole Point of MUT_ARR_PTRS_CLEAN.
+           if (get_itbl((StgClosure *)p)->type == MUT_ARR_PTRS_CLEAN) {
+               recordMutableGen((StgClosure *)p,gen);
+               continue;
+           }
+
            if (scavenge_one(p)) {
                /* didn't manage to promote everything, so put the
                 * object back on the list.
 
        return pap_sizeW((StgPAP *)p);
     case ARR_WORDS:
        return arr_words_sizeW((StgArrWords *)p);
-    case MUT_ARR_PTRS:
+    case MUT_ARR_PTRS_CLEAN:
+    case MUT_ARR_PTRS_DIRTY:
     case MUT_ARR_PTRS_FROZEN:
     case MUT_ARR_PTRS_FROZEN0:
        return mut_arr_ptrs_sizeW((StgMutArrPtrs*)p);
       // nothing to follow 
       continue;
 
-    case MUT_ARR_PTRS:
+    case MUT_ARR_PTRS_CLEAN:
+    case MUT_ARR_PTRS_DIRTY:
     case MUT_ARR_PTRS_FROZEN:
     case MUT_ARR_PTRS_FROZEN0:
       // follow everything 
     case ARR_WORDS:
        return p + arr_words_sizeW((StgArrWords *)p);
        
-    case MUT_ARR_PTRS:
+    case MUT_ARR_PTRS_CLEAN:
+    case MUT_ARR_PTRS_DIRTY:
     case MUT_ARR_PTRS_FROZEN:
     case MUT_ARR_PTRS_FROZEN0:
        // follow everything 
 
        size = sizeofW(StgMVar);
        return size;
 
-    case MUT_ARR_PTRS:
+    case MUT_ARR_PTRS_CLEAN:
+    case MUT_ARR_PTRS_DIRTY:
     case MUT_ARR_PTRS_FROZEN:
     case MUT_ARR_PTRS_FROZEN0:
        size = mut_arr_ptrs_sizeW((StgMutArrPtrs *)c);
 
     "ptr" arr = foreign "C" allocateLocal(MyCapability() "ptr",words) [];
     TICK_ALLOC_PRIM(SIZEOF_StgMutArrPtrs, WDS(n), 0);
 
-    SET_HDR(arr, stg_MUT_ARR_PTRS_info, W_[CCCS]);
+    SET_HDR(arr, stg_MUT_ARR_PTRS_DIRTY_info, W_[CCCS]);
     StgMutArrPtrs_ptrs(arr) = n;
 
     // Initialise all elements of the the array with the value in R2
   // multiple times during GC, which would be unnecessarily slow.
   //
   if (StgHeader_info(R1) != stg_MUT_ARR_PTRS_FROZEN0_info) {
-       SET_INFO(R1,stg_MUT_ARR_PTRS_info);
+       SET_INFO(R1,stg_MUT_ARR_PTRS_DIRTY_info);
        foreign "C" recordMutableLock(R1 "ptr") [R1];
        // must be done after SET_INFO, because it ASSERTs closure_MUTABLE()
        RET_P(R1);
   } else {
-       SET_INFO(R1,stg_MUT_ARR_PTRS_info);
+       SET_INFO(R1,stg_MUT_ARR_PTRS_DIRTY_info);
        RET_P(R1);
   }
 }
 
             break;
         }
 
-    case MUT_ARR_PTRS:
-       debugBelch("MUT_ARR_PTRS(size=%lu)\n", (lnat)((StgMutArrPtrs *)obj)->ptrs);
+    case MUT_ARR_PTRS_CLEAN:
+       debugBelch("MUT_ARR_PTRS_CLEAN(size=%lu)\n", (lnat)((StgMutArrPtrs *)obj)->ptrs);
+       break;
+
+    case MUT_ARR_PTRS_DIRTY:
+       debugBelch("MUT_ARR_PTRS_DIRTY(size=%lu)\n", (lnat)((StgMutArrPtrs *)obj)->ptrs);
        break;
 
     case MUT_ARR_PTRS_FROZEN:
 
 
     , "ARR_WORDS"
 
-    , "MUT_ARR_PTRS"
+    , "MUT_ARR_PTRS_CLEAN"
+    , "MUT_ARR_PTRS_DIRTY"
     , "MUT_ARR_PTRS_FROZEN"
     , "MUT_VAR"
 
                size = arr_words_sizeW(stgCast(StgArrWords*,p));
                break;
                
-           case MUT_ARR_PTRS:
+           case MUT_ARR_PTRS_CLEAN:
+           case MUT_ARR_PTRS_DIRTY:
            case MUT_ARR_PTRS_FROZEN:
            case MUT_ARR_PTRS_FROZEN0:
                prim = rtsTrue;
 
        break;
 
        // StgMutArrPtr.ptrs, no SRT
-    case MUT_ARR_PTRS:
+    case MUT_ARR_PTRS_CLEAN:
+    case MUT_ARR_PTRS_DIRTY:
     case MUT_ARR_PTRS_FROZEN:
     case MUT_ARR_PTRS_FROZEN0:
        init_ptrs(&se.info, ((StgMutArrPtrs *)c)->ptrs,
        case BCO:
        case CONSTR_STATIC:
            // StgMutArrPtr.ptrs, no SRT
-       case MUT_ARR_PTRS:
+       case MUT_ARR_PTRS_CLEAN:
+       case MUT_ARR_PTRS_DIRTY:
        case MUT_ARR_PTRS_FROZEN:
        case MUT_ARR_PTRS_FROZEN0:
            *c = find_ptrs(&se->info);
        // mutable objects
     case MVAR:
     case MUT_VAR:
-    case MUT_ARR_PTRS:
+    case MUT_ARR_PTRS_CLEAN:
+    case MUT_ARR_PTRS_DIRTY:
     case MUT_ARR_PTRS_FROZEN:
     case MUT_ARR_PTRS_FROZEN0:
 
     case MVAR:
        return sizeofW(StgMVar);
 
-    case MUT_ARR_PTRS:
+    case MUT_ARR_PTRS_CLEAN:
+    case MUT_ARR_PTRS_DIRTY:
     case MUT_ARR_PTRS_FROZEN:
     case MUT_ARR_PTRS_FROZEN0:
        return mut_arr_ptrs_sizeW((StgMutArrPtrs *)c);
 
     case ARR_WORDS:
            return arr_words_sizeW((StgArrWords *)p);
 
-    case MUT_ARR_PTRS:
+    case MUT_ARR_PTRS_CLEAN:
+    case MUT_ARR_PTRS_DIRTY:
     case MUT_ARR_PTRS_FROZEN:
     case MUT_ARR_PTRS_FROZEN0:
        {
 
 INFO_TABLE(stg_ARR_WORDS, 0, 0, ARR_WORDS, "ARR_WORDS", "ARR_WORDS")
 { foreign "C" barf("ARR_WORDS object entered!"); }
 
-INFO_TABLE(stg_MUT_ARR_PTRS, 0, 0, MUT_ARR_PTRS, "MUT_ARR_PTRS", "MUT_ARR_PTRS")
-{ foreign "C" barf("MUT_ARR_PTRS object entered!"); }
+INFO_TABLE(stg_MUT_ARR_PTRS_CLEAN, 0, 0, MUT_ARR_PTRS_CLEAN, "MUT_ARR_PTRS_CLEAN", "MUT_ARR_PTRS_CLEAN")
+{ foreign "C" barf("MUT_ARR_PTRS_CLEAN object entered!"); }
+
+INFO_TABLE(stg_MUT_ARR_PTRS_DIRTY, 0, 0, MUT_ARR_PTRS_DIRTY, "MUT_ARR_PTRS_DIRTY", "MUT_ARR_PTRS_DIRTY")
+{ foreign "C" barf("MUT_ARR_PTRS_DIRTY object entered!"); }
 
 INFO_TABLE(stg_MUT_ARR_PTRS_FROZEN, 0, 0, MUT_ARR_PTRS_FROZEN, "MUT_ARR_PTRS_FROZEN", "MUT_ARR_PTRS_FROZEN")
 { foreign "C" barf("MUT_ARR_PTRS_FROZEN object entered!"); }