Quicker partial collection by using card marking.

Add calls to the card marking from the write barrier routines, so that
a write to an Object marks the appropriate card. Add code in the GC to
use and rebuild the cards at a partial GC, clearing cards in the
Zygote heap which do not in fact contain references to the application
heap.

Change-Id: Ie6f29fd096e029f48085715b282b6db8a7122555
diff --git a/vm/Dalvik.h b/vm/Dalvik.h
index 5d84440..6418bf2 100644
--- a/vm/Dalvik.h
+++ b/vm/Dalvik.h
@@ -55,6 +55,7 @@
 #include "oo/Array.h"
 #include "Exception.h"
 #include "alloc/Alloc.h"
+#include "alloc/CardTable.h"
 #include "alloc/HeapDebug.h"
 #include "alloc/HeapWorker.h"
 #include "alloc/GC.h"
diff --git a/vm/Dvm.mk b/vm/Dvm.mk
index 3214e31..8626375 100644
--- a/vm/Dvm.mk
+++ b/vm/Dvm.mk
@@ -130,6 +130,7 @@
 	UtfString.c \
 	alloc/clz.c.arm \
 	alloc/Alloc.c \
+	alloc/CardTable.c \
 	alloc/HeapBitmap.c.arm \
 	alloc/HeapDebug.c \
 	alloc/HeapTable.c \
diff --git a/vm/Globals.h b/vm/Globals.h
index e9b6ef3..60d67c8 100644
--- a/vm/Globals.h
+++ b/vm/Globals.h
@@ -123,6 +123,7 @@
     bool        postVerify;
     bool        generateRegisterMaps;
     bool        concurrentMarkSweep;
+    bool        verifyCardTable;
 
     int         assertionCtrlCount;
     AssertionControl*   assertionCtrl;
diff --git a/vm/Init.c b/vm/Init.c
index e29687d..ca54a64 100644
--- a/vm/Init.c
+++ b/vm/Init.c
@@ -118,6 +118,7 @@
     dvmFprintf(stderr, "  -Xgc:[no]preverify\n");
     dvmFprintf(stderr, "  -Xgc:[no]postverify\n");
     dvmFprintf(stderr, "  -Xgc:[no]concurrent\n");
+    dvmFprintf(stderr, "  -Xgc:[no]verifycardtable\n");
     dvmFprintf(stderr, "  -Xgenregmap\n");
     dvmFprintf(stderr, "  -Xcheckdexsum\n");
 #if defined(WITH_JIT)
@@ -988,6 +989,10 @@
                 gDvm.concurrentMarkSweep = true;
             else if (strcmp(argv[i] + 5, "noconcurrent") == 0)
                 gDvm.concurrentMarkSweep = false;
+            else if (strcmp(argv[i] + 5, "verifycardtable") == 0)
+                gDvm.verifyCardTable = true;
+            else if (strcmp(argv[i] + 5, "noverifycardtable") == 0)
+                gDvm.verifyCardTable = false;
             else {
                 dvmFprintf(stderr, "Bad value for -Xgc");
                 return -1;
diff --git a/vm/Misc.c b/vm/Misc.c
index 21b7fce..2a1e244 100644
--- a/vm/Misc.c
+++ b/vm/Misc.c
@@ -26,7 +26,11 @@
 #include <time.h>
 #include <sys/time.h>
 #include <fcntl.h>
+#include <cutils/ashmem.h>
+#include <sys/mman.h>
 
+#define ALIGN_UP_TO_PAGE_SIZE(p) \
+    (((size_t)(p) + (SYSTEM_PAGE_SIZE - 1)) & ~(SYSTEM_PAGE_SIZE - 1))
 
 /*
  * Print a hex dump in this format:
@@ -686,3 +690,28 @@
     return srcLength;
 }
 #endif
+
+/*
+ *  Allocates a memory region using ashmem and mmap, initialized to
+ *  zero.  Actual allocation rounded up to page multiple.  Returns
+ *  NULL on failure.
+ */
+void *dvmAllocRegion(size_t size, int prot, const char *name) {
+    void *base;
+    int fd, ret;
+
+    size = ALIGN_UP_TO_PAGE_SIZE(size);
+    fd = ashmem_create_region(name, size);
+    if (fd == -1) {
+        return NULL;
+    }
+    base = mmap(NULL, size, prot, MAP_PRIVATE, fd, 0);
+    ret = close(fd);
+    if (base == MAP_FAILED) {
+        return NULL;
+    }
+    if (ret == -1) {
+        return NULL;
+    }
+    return base;
+}
diff --git a/vm/Misc.h b/vm/Misc.h
index 3e04f84..a6e46c0 100644
--- a/vm/Misc.h
+++ b/vm/Misc.h
@@ -287,4 +287,11 @@
 size_t strlcpy(char *dst, const char *src, size_t size);
 #endif
 
+/*
+ *  Allocates a memory region using ashmem and mmap, initialized to
+ *  zero.  Actual allocation rounded up to page multiple.  Returns
+ *  NULL on failure.
+ */
+void *dvmAllocRegion(size_t size, int prot, const char *name);
+
 #endif /*_DALVIK_MISC*/
diff --git a/vm/alloc/CardTable.c b/vm/alloc/CardTable.c
new file mode 100644
index 0000000..9455955
--- /dev/null
+++ b/vm/alloc/CardTable.c
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Needed for PROT_* definitions.
+ */
+#include <sys/mman.h>
+
+#include "Dalvik.h"
+#include "alloc/HeapSource.h"
+#include "alloc/Visit.h"
+
+/*
+ * Maintain a card table from the the write barrier. All writes of
+ * non-NULL values to heap addresses should go through an entry in
+ * WriteBarrier, and from there to here.
+ *
+ * The heap is divided into "cards" of 512 bytes, as determined by
+ * GC_CARD_SHIFT. The card table contains one byte of data per card,
+ * to be used by the GC. The value of the byte will be one of
+ * GC_CARD_CLEAN or GC_CARD_DIRTY.
+ *
+ * After any store of a non-NULL object pointer into a heap object,
+ * code is obliged to mark the card dirty. The setters in
+ * ObjectInlines.h [such as dvmSetFieldObject] do this for you. The
+ * JIT and fast interpreters also contain code to mark cards as dirty.
+ *
+ * [TODO: Concurrent collection will have to expand on this, as it
+ * uses the card table as well.]
+ *
+ * The card table is used to support partial collection, which at the
+ * moment means "treat the zygote's heap as permanent, and only GC
+ * objects in the application heap". In order to do this efficiently,
+ * the GC need to find quickly references to objects in the
+ * application heap from the zygote heap.  When an application creates
+ * an object and stores it into an object on the zygote heap, it will
+ * mark the corresponding card in the zygote heap as "dirty". When the
+ * GC does a partial collection, it can efficiently find all the
+ * cross-heap objects, since they are all on dirty cards. The GC also
+ * takes the opportunity to mark as "clean" any cards which are dirty,
+ * but no longer contain cross-heap pointers.
+ *
+ * The card table's base [the "biased card table"] gets set to a
+ * rather strange value.  In order to keep the JIT from having to
+ * fabricate or load GC_DIRTY_CARD to store into the card table,
+ * biased base is within the mmap allocation at a point where it's low
+ * byte is equal to GC_DIRTY_CARD. See dvmCardTableStartup for details.
+ */
+
+/*
+ * Initializes the card table; must be called before any other
+ * dvmCardTable*() functions.
+ */
+bool dvmCardTableStartup(GcHeap *gcHeap, void *heapBase)
+{
+    size_t length;
+    void *allocBase;
+    u1 *biasedBase;
+
+    /* Set up the card table */
+    length = gDvm.heapSizeMax / GC_CARD_SIZE;
+    /* Allocate an extra 256 bytes to allow fixed low-byte of base */
+    allocBase = dvmAllocRegion(length + 0x100, PROT_READ | PROT_WRITE,
+                            "dalvik-card-table");
+    if (allocBase == NULL) {
+        return false;
+    }
+    gcHeap->cardTableBase = allocBase;
+    gcHeap->cardTableLength = length;
+    /* All zeros is the correct initial value; all clean. */
+    assert(GC_CARD_CLEAN == 0);
+
+    biasedBase = (u1 *)((uintptr_t)allocBase -
+                        ((uintptr_t)heapBase >> GC_CARD_SHIFT));
+    if (((uintptr_t)biasedBase & 0xff) != GC_CARD_DIRTY) {
+        int offset;
+        offset = GC_CARD_DIRTY - ((uintptr_t)biasedBase & 0xff);
+        biasedBase += offset + (offset < 0 ? 0x100 : 0);
+    }
+    assert(((uintptr_t)biasedBase & 0xff) == GC_CARD_DIRTY);
+    gcHeap->biasedCardTableBase = biasedBase;
+
+    return true;
+}
+
+/*
+ * Tears down the entire CardTable.
+ */
+void dvmCardTableShutdown()
+{
+    munmap(gDvm.gcHeap->cardTableBase, gDvm.gcHeap->cardTableLength);
+}
+
+/*
+ * Returns The address of the relevent byte in the card table, given
+ * an address on the heap.
+ */
+u1 *dvmCardFromAddr(const void *addr)
+{
+    u1 *cardAddr = gDvm.gcHeap->biasedCardTableBase +
+        ((uintptr_t)addr >> GC_CARD_SHIFT);
+    assert(cardAddr >= gDvm.gcHeap->cardTableBase);
+    assert(cardAddr <
+           &gDvm.gcHeap->cardTableBase[gDvm.gcHeap->cardTableLength]);
+    return cardAddr;
+}
+
+void *dvmAddrFromCard(const u1 *cardAddr) {
+    assert(cardAddr >= gDvm.gcHeap->cardTableBase);
+    assert(cardAddr <
+           &gDvm.gcHeap->cardTableBase[gDvm.gcHeap->cardTableLength]);
+    void *addr = (void *)((cardAddr - gDvm.gcHeap->biasedCardTableBase) << GC_CARD_SHIFT);
+    return addr;
+}
+
+/*
+ * Dirties the card for the given address.
+ */
+void dvmMarkCard(const void *addr)
+{
+    u1 *cardAddr = dvmCardFromAddr(addr);
+    *cardAddr = GC_CARD_DIRTY;
+}
+
+/*
+ * Returns true iff all address within the Object are on unmarked cards.
+ */
+static bool objectIsClean(const Object *obj)
+{
+    assert(dvmIsValidObject(obj));
+    size_t size = dvmHeapSourceChunkSize(obj);
+    u1 *start = dvmCardFromAddr(obj);
+    u1 *end = dvmCardFromAddr((char *)obj + size-1);
+    u1 *index;
+
+    for (index = start; index <= end; index++) {
+        if (*index != GC_CARD_CLEAN) {
+            return false;
+        }
+    }
+    return true;
+}
+
+/*
+ * A Visitor callback in support of checkCleanObjects. "arg" is
+ * expected to be the immuneLimit.
+ */
+static void
+crossGenCheckVisitor(void *ptr, void *arg)
+{
+    Object *ref = *(Object **)ptr;
+    Object *immuneLimit = (Object *)arg;
+
+    if (ref >= immuneLimit) {
+        LOGE("Clean obj contains threatened ref %p: %p", ptr, ref);
+        dvmAbort();
+    }
+}
+
+/*
+ * A HeapBitmap callback in support of checkCleanObjects.
+ */
+static bool
+crossGenCheckCallback(size_t numPtrs, void **ptrs,
+                      const void *finger, void *arg)
+{
+    size_t i;
+    for (i = 0; i < numPtrs; i++) {
+        Object *obj = ptrs[i];
+        if (objectIsClean(obj)) {
+            dvmVisitObject(crossGenCheckVisitor, obj, arg);
+        }
+    }
+
+    return true;
+}
+
+/*
+ * dvmAbort if a clean, immune Object in the bitmap contains a pointer
+ * to a threatened Object.
+ */
+void dvmVerifyCardTable(HeapBitmap *bitmap, const char *immuneLimit)
+{
+    dvmHeapBitmapWalk(bitmap, crossGenCheckCallback, (void *)immuneLimit);
+}
+
diff --git a/vm/alloc/CardTable.h b/vm/alloc/CardTable.h
new file mode 100644
index 0000000..c142702
--- /dev/null
+++ b/vm/alloc/CardTable.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Maintain a card table from the the write barrier. All writes of
+ * non-NULL values to heap addresses should go through an entry in
+ * WriteBarrier, and from there to here.
+ */
+
+#ifndef _DALVIK_ALLOC_CARDTABLE
+#define _DALVIK_ALLOC_CARDTABLE
+
+/*
+ * TODO: Better documentation of these values pending integration of
+ * concurrent collections and the card table.
+ */
+#define GC_CARD_SHIFT 7
+#define GC_CARD_SIZE (1 << GC_CARD_SHIFT)
+#define GC_OBJ_PER_CARD (GC_CARD_SIZE >> 3)
+#define GC_CARD_CLEAN 0
+#define GC_CARD_DIRTY 0x70
+
+struct GcHeap;
+struct HeapBitmap;
+
+/*
+ * Initializes the card table; must be called before any other
+ * dvmCardTable*() functions.
+ */
+bool dvmCardTableStartup(struct GcHeap *gcHeap, void *heapBase);
+
+/*
+ * Tears down the entire CardTable structure.
+ */
+void dvmCardTableShutdown(void);
+
+/*
+ * Returns The address of the relevent byte in the card table, given
+ * an address on the heap.
+ */
+u1 *dvmCardFromAddr(const void *addr);
+
+void *dvmAddrFromCard(const u1 *card);
+
+/*
+ * Set the card associated with the given address to GC_CARD_DIRTY.
+ */
+void dvmMarkCard(const void *addr);
+
+/*
+ * dvmAbort if a clean, immune Object in the bitmap contains a pointer
+ * to a threatened Object.
+ */
+void dvmVerifyCardTable(struct HeapBitmap *bitmap, const char *immuneLimit);
+
+/* TODO: Clearing, querying, and iterating over the card table. */
+
+#endif /*_DALVIK_ALLOC_CARDTABLE*/
diff --git a/vm/alloc/HeapBitmap.h b/vm/alloc/HeapBitmap.h
index fd35858..5b1ed95 100644
--- a/vm/alloc/HeapBitmap.h
+++ b/vm/alloc/HeapBitmap.h
@@ -50,7 +50,7 @@
     static inline p
 
 
-typedef struct {
+struct HeapBitmap {
     /* The bitmap data, which points to an mmap()ed area of zeroed
      * anonymous memory.
      */
@@ -77,7 +77,8 @@
      * to a set bit.  If there are no bits set, (max < base).
      */
     uintptr_t max;
-} HeapBitmap;
+};
+typedef struct HeapBitmap HeapBitmap;
 
 
 /*
diff --git a/vm/alloc/HeapInternal.h b/vm/alloc/HeapInternal.h
index 728af95..c592a01 100644
--- a/vm/alloc/HeapInternal.h
+++ b/vm/alloc/HeapInternal.h
@@ -93,6 +93,11 @@
      */
     GcMarkContext   markContext;
 
+    /* GC's card table */
+    u1*             cardTableBase;
+    size_t          cardTableLength;
+    u1*             biasedCardTableBase;
+
     /* Set to dvmGetRelativeTimeUsec() whenever a GC begins.
      * The value is preserved between GCs, so it can be used
      * to determine the time between successive GCs.
diff --git a/vm/alloc/HeapSource.c b/vm/alloc/HeapSource.c
index 86f9207..f55ccc5 100644
--- a/vm/alloc/HeapSource.c
+++ b/vm/alloc/HeapSource.c
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-#include <cutils/ashmem.h>
 #include <cutils/mspace.h>
 #include <limits.h>     // for INT_MAX
 #include <sys/mman.h>
@@ -473,7 +472,6 @@
     mspace msp;
     size_t length;
     void *base;
-    int fd, ret;
 
     assert(gHs == NULL);
 
@@ -488,18 +486,10 @@
      * among the heaps managed by the garbage collector.
      */
     length = ALIGN_UP_TO_PAGE_SIZE(absoluteMaxSize);
-    fd = ashmem_create_region("dalvik-heap", length);
-    if (fd == -1) {
+    base = dvmAllocRegion(length, PROT_NONE, "dalvik-heap");
+    if (base == NULL) {
         return NULL;
     }
-    base = mmap(NULL, length, PROT_NONE, MAP_PRIVATE, fd, 0);
-    if (base == MAP_FAILED) {
-        return NULL;
-    }
-    ret = close(fd);
-    if (ret == -1) {
-        goto fail;
-    }
 
     /* Create an unlocked dlmalloc mspace to use as
      * the small object heap source.
@@ -557,6 +547,10 @@
     countAllocation(hs2heap(hs), hs, false);
 
     gHs = hs;
+    if (!dvmCardTableStartup(gcHeap, base)) {
+        LOGE_HEAP("card table allocation failed.");
+        goto fail;
+    }
     return gcHeap;
 
 fail:
diff --git a/vm/alloc/MarkSweep.c b/vm/alloc/MarkSweep.c
index 1abdffa..cdab034 100644
--- a/vm/alloc/MarkSweep.c
+++ b/vm/alloc/MarkSweep.c
@@ -483,6 +483,160 @@
     }
 }
 
+/*
+ * Variants for partial GC. Scan immune objects, and rebuild the card
+ * table.
+ */
+
+/*
+ * Mark an object which was found in an immune object.
+ */
+static void scanImmuneReference(const Object *obj, GcMarkContext *ctx)
+{
+    if (obj != NULL) {
+        if (obj < (Object *)ctx->immuneLimit) {
+            assert(isMarked(obj, ctx));
+        } else {
+            ctx->crossGen = true;
+            markObjectNonNull(obj, ctx, true, false);
+        }
+    }
+}
+
+/*
+ * Scans instance fields.
+ */
+static void scanImmuneInstanceFields(const Object *obj, GcMarkContext *ctx)
+{
+    assert(obj != NULL);
+    assert(obj->clazz != NULL);
+    assert(ctx != NULL);
+
+    if (obj->clazz->refOffsets != CLASS_WALK_SUPER) {
+        unsigned int refOffsets = obj->clazz->refOffsets;
+        while (refOffsets != 0) {
+            const int rshift = CLZ(refOffsets);
+            refOffsets &= ~(CLASS_HIGH_BIT >> rshift);
+            scanImmuneReference(
+                dvmGetFieldObject((Object*)obj, CLASS_OFFSET_FROM_CLZ(rshift)),
+                ctx);
+        }
+    } else {
+        ClassObject *clazz;
+        int i;
+        for (clazz = obj->clazz; clazz != NULL; clazz = clazz->super) {
+            InstField *field = clazz->ifields;
+            for (i = 0; i < clazz->ifieldRefCount; ++i, ++field) {
+                void *addr = BYTE_OFFSET((Object *)obj, field->byteOffset);
+                scanImmuneReference(((JValue *)addr)->l, ctx);
+            }
+        }
+    }
+}
+
+/*
+ * Scans the header, static field references, and interface
+ * pointers of a class object.
+ */
+static void scanImmuneClassObject(const ClassObject *obj, GcMarkContext *ctx)
+{
+    int i;
+
+    assert(obj != NULL);
+    assert(obj->obj.clazz == gDvm.classJavaLangClass);
+    assert(ctx != NULL);
+
+    scanImmuneReference((Object *)obj->obj.clazz, ctx);
+    if (IS_CLASS_FLAG_SET(obj, CLASS_ISARRAY)) {
+        scanImmuneReference((Object *)obj->elementClass, ctx);
+    }
+    /* Do super and the interfaces contain Objects and not dex idx values? */
+    if (obj->status > CLASS_IDX) {
+        scanImmuneReference((Object *)obj->super, ctx);
+    }
+    scanImmuneReference(obj->classLoader, ctx);
+    /* Scan static field references. */
+    for (i = 0; i < obj->sfieldCount; ++i) {
+        char ch = obj->sfields[i].field.signature[0];
+        if (ch == '[' || ch == 'L') {
+            scanImmuneReference(obj->sfields[i].value.l, ctx);
+        }
+    }
+    /* Scan the instance fields. */
+    scanImmuneInstanceFields((const Object *)obj, ctx);
+    /* Scan interface references. */
+    if (obj->status > CLASS_IDX) {
+        for (i = 0; i < obj->interfaceCount; ++i) {
+            scanImmuneReference((Object *)obj->interfaces[i], ctx);
+        }
+    }
+}
+
+/*
+ * Scans the header of all array objects.  If the array object is
+ * specialized to a reference type, scans the array data as well.
+ */
+static void scanImmuneArrayObject(const ArrayObject *obj, GcMarkContext *ctx)
+{
+    size_t i;
+
+    assert(obj != NULL);
+    assert(obj->obj.clazz != NULL);
+    assert(ctx != NULL);
+    /* Scan the class object reference. */
+    scanImmuneReference((Object *)obj->obj.clazz, ctx);
+    if (IS_CLASS_FLAG_SET(obj->obj.clazz, CLASS_ISOBJECTARRAY)) {
+        /* Scan the array contents. */
+        Object **contents = (Object **)obj->contents;
+        for (i = 0; i < obj->length; ++i) {
+            scanImmuneReference(contents[i], ctx);
+        }
+    }
+}
+
+/*
+ * Scans the header and field references of a data object.
+ */
+static void scanImmuneDataObject(DataObject *obj, GcMarkContext *ctx)
+{
+    assert(obj != NULL);
+    assert(obj->obj.clazz != NULL);
+    assert(ctx != NULL);
+    /* Scan the class object. */
+    scanImmuneReference((Object *)obj->obj.clazz, ctx);
+    /* Scan the instance fields. */
+    scanImmuneInstanceFields((const Object *)obj, ctx);
+    if (IS_CLASS_FLAG_SET(obj->obj.clazz, CLASS_ISREFERENCE)) {
+        scanImmuneReference((Object *)obj, ctx);
+    }
+}
+
+/*
+ * Scans an object reference.  Determines the type of the reference
+ * and dispatches to a specialized scanning routine.
+ */
+static void scanImmuneObject(const Object *obj, GcMarkContext *ctx)
+{
+    assert(obj != NULL);
+    assert(obj->clazz != NULL);
+    assert(ctx != NULL);
+    assert(obj < (Object *)ctx->immuneLimit);
+
+#if WITH_HPROF
+    if (gDvm.gcHeap->hprofContext != NULL) {
+        hprofDumpHeapObject(gDvm.gcHeap->hprofContext, obj);
+    }
+#endif
+    /* Dispatch a type-specific scan routine. */
+    if (obj->clazz == gDvm.classJavaLangClass) {
+        scanImmuneClassObject((ClassObject *)obj, ctx);
+    } else if (IS_CLASS_FLAG_SET(obj->clazz, CLASS_ISARRAY)) {
+        scanImmuneArrayObject((ArrayObject *)obj, ctx);
+    } else {
+        scanImmuneDataObject((DataObject *)obj, ctx);
+    }
+}
+
 static void
 processMarkStack(GcMarkContext *ctx)
 {
@@ -537,8 +691,62 @@
 #ifndef NDEBUG
     gLastFinger = 0;
 #endif
-    dvmHeapBitmapWalk(ctx->bitmap, scanBitmapCallback, ctx);
+    if (gDvm.executionMode == kExecutionModeInterpPortable) {
+        /* The portable interpreter dirties cards on write; other
+         * modes do not yet do so.
+         * TODO: Bring the fast interpreter and JIT into the fold.
+         */
+        HeapBitmap markBits[HEAP_SOURCE_MAX_HEAP_COUNT];
+        HeapBitmap liveBits[HEAP_SOURCE_MAX_HEAP_COUNT];
+        size_t numBitmaps, i;
+        numBitmaps = dvmHeapSourceGetNumHeaps();
+        dvmHeapSourceGetObjectBitmaps(liveBits, markBits, numBitmaps);
+        for (i = 0; i < numBitmaps; i++) {
+            /* The use of finger to tell visited from unvisited objects
+             * requires we walk the bitmaps from low to high
+             * addresses. This code assumes [and asserts] that the order
+             * of the heaps returned is the reverse of that.
+             */
+            size_t j = numBitmaps-1-i;
+            assert(j == 0 || (markBits[j].base < markBits[j-1].base));
+            if (markBits[j].base < (uintptr_t)ctx->immuneLimit) {
+                if (gDvm.verifyCardTable) {
+                    dvmVerifyCardTable(&markBits[j], ctx->immuneLimit);
+                }
+                uintptr_t minAddr = markBits[j].base;
+                uintptr_t maxAddr = markBits[j].base +
+                    HB_MAX_OFFSET(&markBits[j]);
+                u1 *minCard = dvmCardFromAddr((void *)minAddr);
+                u1 *maxCard = dvmCardFromAddr((void *)maxAddr);
 
+                u1 *card;
+                /* TODO: This double-loop should be made faster.
+                 */
+                for (card = minCard; card <= maxCard; card++) {
+                    if (*card == GC_CARD_DIRTY) {
+                        uintptr_t addr = (uintptr_t)dvmAddrFromCard(card);
+                        uintptr_t endAddr = addr + GC_CARD_SIZE;
+                        ctx->crossGen  = false;
+                        for ( ; addr < endAddr; addr += 8) {
+                            if (dvmIsValidObject((void *)addr)) {
+                                scanImmuneObject((void *)addr, ctx);
+                            }
+                        }
+                        if (! ctx->crossGen) {
+                            *card = GC_CARD_CLEAN;
+                        }
+                    }
+                }
+                if (gDvm.verifyCardTable) {
+                    dvmVerifyCardTable(&markBits[j], ctx->immuneLimit);
+                }
+            } else {
+                dvmHeapBitmapWalk(&markBits[j], scanBitmapCallback, ctx);
+            }
+        }
+    } else {
+        dvmHeapBitmapWalk(ctx->bitmap, scanBitmapCallback, ctx);
+    }
     /* We've walked the mark bitmaps.  Scan anything that's
      * left on the mark stack.
      */
diff --git a/vm/alloc/MarkSweep.h b/vm/alloc/MarkSweep.h
index d344aae..e143b35 100644
--- a/vm/alloc/MarkSweep.h
+++ b/vm/alloc/MarkSweep.h
@@ -42,6 +42,7 @@
     GcMarkStack stack;
     const char *immuneLimit;
     const void *finger;   // only used while scanning/recursing.
+    bool crossGen;        // used when scanning immune objects.
 } GcMarkContext;
 
 bool dvmHeapBeginMarkStep(GcMode mode);
diff --git a/vm/alloc/WriteBarrier.h b/vm/alloc/WriteBarrier.h
index fef993e..34b3b19 100644
--- a/vm/alloc/WriteBarrier.h
+++ b/vm/alloc/WriteBarrier.h
@@ -23,13 +23,17 @@
 /*
  * The address within the Object has been written, and perhaps changed.
  */
-INLINE void dvmWriteBarrierField(const Object *obj, void *addr) {
+INLINE void dvmWriteBarrierField(const Object *obj, void *addr)
+{
+    dvmMarkCard(obj);
 }
 
 /*
  * All of the Object may have changed.
  */
-INLINE void dvmWriteBarrierObject(const Object *obj) {
+INLINE void dvmWriteBarrierObject(const Object *obj)
+{
+    dvmMarkCard(obj);
 }
 
 /*
@@ -37,6 +41,8 @@
  * or equal to start and strictly less than end, have been written,
  * and perhaps changed.
  */
-INLINE void dvmWriteBarrierArray(const ArrayObject* obj,
-                                 size_t start, size_t end) {
+INLINE void dvmWriteBarrierArray(const ArrayObject *obj,
+                                 size_t start, size_t end)
+{
+    dvmMarkCard((Object *)obj);
 }