Merge change 9414

* changes:
  Fix a LOGVV.
diff --git a/libcore/dalvik/src/main/java/dalvik/system/VMDebug.java b/libcore/dalvik/src/main/java/dalvik/system/VMDebug.java
index 06a67b6..bcc8b61 100644
--- a/libcore/dalvik/src/main/java/dalvik/system/VMDebug.java
+++ b/libcore/dalvik/src/main/java/dalvik/system/VMDebug.java
@@ -282,8 +282,15 @@
      */
     public static native boolean cacheRegisterMap(String classAndMethodDesc);
 
-    /* don't ask */
-    static native void printThis(Object thisThing, int count, int thing);
+    /**
+     * Crashes the VM.  Seriously.  Dumps the stack trace for the current
+     * thread and then aborts the VM so you can see the native stack trace.
+     * Useful for figuring out how you got somewhere when lots of native
+     * code is involved.
+     *
+     * @hide
+     */
+    public static native void crash();
 
     /*
      * Fake method, inserted into dmtrace output when the garbage collector
diff --git a/vm/Android.mk b/vm/Android.mk
index 8f873d4..48b53af 100644
--- a/vm/Android.mk
+++ b/vm/Android.mk
@@ -57,7 +57,7 @@
   # - allocation limits enabled
   # - GDB helpers enabled
   # - LOGV
-  # - assert()  (NDEBUG is handled in the build system)
+  # - assert()
   #
   LOCAL_CFLAGS += -DWITH_INSTR_CHECKS
   LOCAL_CFLAGS += -DWITH_EXTRA_OBJECT_VALIDATION
@@ -69,6 +69,8 @@
   LOCAL_CFLAGS += -DDVM_SHOW_EXCEPTION=3
   # add some extra stuff to make it easier to examine with GDB
   LOCAL_CFLAGS += -DEASY_GDB
+  # overall config may be for a "release" build, so reconfigure these
+  LOCAL_CFLAGS += -UNDEBUG -DDEBUG=1 -DLOG_NDEBUG=1 -DWITH_DALVIK_ASSERT
 else  # !DALVIK_VM_DEBUG
   #
   # "Performance" profile:
@@ -98,6 +100,7 @@
 	DvmDex.c \
 	Exception.c \
 	Hash.c \
+	IndirectRefTable.c.arm \
 	Init.c \
 	InlineNative.c.arm \
 	Inlines.c \
@@ -182,7 +185,8 @@
 	reflect/Proxy.c \
 	reflect/Reflect.c \
 	test/AtomicSpeed.c \
-	test/TestHash.c
+	test/TestHash.c \
+	test/TestIndirectRefTable.c
 
 ifeq ($(WITH_JIT_TUNING),true)
   LOCAL_CFLAGS += -DWITH_JIT_TUNING
diff --git a/vm/Common.h b/vm/Common.h
index 8ca5224..4b357e2 100644
--- a/vm/Common.h
+++ b/vm/Common.h
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 /*
  * Common defines for all Dalvik code.
  */
@@ -29,8 +30,8 @@
 #if !defined(NDEBUG) && defined(WITH_DALVIK_ASSERT)
 # undef assert
 # define assert(x) \
-    ((x) ? ((void)0) : (LOGE("ASSERT FAILED (%s:%d): " #x "\n", \
-        __FILE__, __LINE__), *(int*)39=39, 0) )
+    ((x) ? ((void)0) : (LOGE("ASSERT FAILED (%s:%d): %s\n", \
+        __FILE__, __LINE__, #x), *(int*)39=39, 0) )
 #endif
 
 
diff --git a/vm/Dalvik.h b/vm/Dalvik.h
index 618d51a..054b838 100644
--- a/vm/Dalvik.h
+++ b/vm/Dalvik.h
@@ -44,6 +44,7 @@
 #include "UtfString.h"
 #include "Intern.h"
 #include "ReferenceTable.h"
+#include "IndirectRefTable.h"
 #include "AtomicCache.h"
 #include "Thread.h"
 #include "Ddm.h"
diff --git a/vm/IndirectRefTable.c b/vm/IndirectRefTable.c
new file mode 100644
index 0000000..0106fe4
--- /dev/null
+++ b/vm/IndirectRefTable.c
@@ -0,0 +1,453 @@
+/*
+ * Copyright (C) 2009 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.
+ */
+
+/*
+ * Indirect reference table management.
+ */
+#include "Dalvik.h"
+
+/*
+ * Initialize an IndirectRefTable structure.
+ */
+bool dvmInitIndirectRefTable(IndirectRefTable* pRef, int initialCount,
+    int maxCount, IndirectRefKind kind)
+{
+    assert(initialCount > 0);
+    assert(initialCount <= maxCount);
+    assert(kind == kIndirectKindLocal || kind == kIndirectKindGlobal);
+
+    pRef->table = (Object**) malloc(initialCount * sizeof(Object*));
+    if (pRef->table == NULL)
+        return false;
+#ifndef NDEBUG
+    memset(pRef->table, 0xd1, initialCount * sizeof(Object*));
+#endif
+    pRef->segmentState.all = IRT_SEGMENT_INIT;
+    pRef->allocEntries = initialCount;
+    pRef->maxEntries = maxCount;
+    pRef->kind = kind;
+
+    return true;
+}
+
+/*
+ * Clears out the contents of a IndirectRefTable, freeing allocated storage.
+ */
+void dvmClearIndirectRefTable(IndirectRefTable* pRef)
+{
+    free(pRef->table);
+    pRef->table = NULL;
+    pRef->allocEntries = pRef->maxEntries = -1;
+}
+
+/*
+ * Remove one or more segments from the top.  The table entry identified
+ * by "cookie" becomes the new top-most entry.
+ *
+ * Returns false if "cookie" is invalid or the table has only one segment.
+ */
+bool dvmPopIndirectRefTableSegmentCheck(IndirectRefTable* pRef, u4 cookie)
+{
+    IRTSegmentState sst;
+
+    /*
+     * The new value for "top" must be <= the current value.  Otherwise
+     * this would represent an expansion of the table.
+     */
+    sst.all = cookie;
+    if (sst.parts.topIndex > pRef->segmentState.parts.topIndex) {
+        LOGE("Attempt to expand table with segment pop (%d to %d)\n",
+            pRef->segmentState.parts.topIndex, sst.parts.topIndex);
+        return false;
+    }
+    if (sst.parts.numHoles >= sst.parts.topIndex) {
+        LOGE("Absurd numHoles in cookie (%d bi=%d)\n",
+            sst.parts.numHoles, sst.parts.topIndex);
+        return false;
+    }
+
+    LOGV("--- after pop, top=%d holes=%d\n",
+        sst.parts.topIndex, sst.parts.numHoles);
+
+    return true;
+}
+
+/*
+ * Make sure that the entry at "idx" is correctly paired with "iref".
+ */
+static bool checkEntry(IndirectRefTable* pRef, IndirectRef iref, int idx)
+{
+    Object* obj = pRef->table[idx];
+    IndirectRef checkRef = dvmObjectToIndirectRef(obj, idx, pRef->kind);
+    if (checkRef != iref) {
+        LOGW("iref mismatch: %p vs %p\n", iref, checkRef);
+        return false;
+    }
+    return true;
+}
+
+/*
+ * Add "obj" to "pRef".
+ */
+IndirectRef dvmAddToIndirectRefTable(IndirectRefTable* pRef, u4 cookie,
+    Object* obj)
+{
+    IRTSegmentState prevState;
+    prevState.all = cookie;
+    int topIndex = pRef->segmentState.parts.topIndex;
+    int bottomIndex = prevState.parts.topIndex;
+
+    assert(obj != NULL);
+    assert(dvmIsValidObject(obj));
+    assert(pRef->table != NULL);
+    assert(pRef->allocEntries <= pRef->maxEntries);
+    assert(pRef->segmentState.parts.numHoles >= prevState.parts.numHoles);
+
+    if (topIndex == pRef->allocEntries) {
+        /* reached end of allocated space; did we hit buffer max? */
+        if (topIndex == pRef->maxEntries) {
+            LOGW("ReferenceTable overflow (max=%d)\n", pRef->maxEntries);
+            return NULL;
+        }
+
+        Object** newTable;
+        int newSize;
+
+        newSize = pRef->allocEntries * 2;
+        if (newSize > pRef->maxEntries)
+            newSize = pRef->maxEntries;
+        assert(newSize > pRef->allocEntries);
+
+        newTable = (Object**) realloc(pRef->table, newSize * sizeof(Object*));
+        if (newTable == NULL) {
+            LOGE("Unable to expand iref table (from %d to %d entries)\n",
+                pRef->allocEntries, newSize);
+            return false;
+        }
+        LOGI("Growing %p from %d to %d\n", pRef, pRef->allocEntries, newSize);
+
+        /* update entries; adjust "nextEntry" in case memory moved */
+        pRef->table = newTable;
+        pRef->allocEntries = newSize;
+    }
+
+    IndirectRef result;
+
+    /*
+     * We know there's enough room in the table.  Now we just need to find
+     * the right spot.  If there's a hole, find it and fill it; otherwise,
+     * add to the end of the list.
+     */
+    int numHoles = pRef->segmentState.parts.numHoles - prevState.parts.numHoles;
+    if (numHoles > 0) {
+        assert(topIndex > 1);
+        /* find the first hole; likely to be near the end of the list */
+        Object** pScan = &pRef->table[topIndex - 1];
+        assert(*pScan != NULL);
+        while (*--pScan != NULL) {
+            assert(pScan >= pRef->table + bottomIndex);
+        }
+        result = dvmObjectToIndirectRef(obj, pScan - pRef->table, pRef->kind);
+        *pScan = obj;
+        pRef->segmentState.parts.numHoles--;
+    } else {
+        /* add to the end */
+        result = dvmObjectToIndirectRef(obj, topIndex, pRef->kind);
+        pRef->table[topIndex++] = obj;
+        pRef->segmentState.parts.topIndex = topIndex;
+    }
+
+    assert(result != NULL);
+    return result;
+}
+
+/*
+ * Verify that the indirect table lookup is valid.
+ *
+ * Returns "false" if something looks bad.
+ */
+bool dvmGetFromIndirectRefTableCheck(IndirectRefTable* pRef, IndirectRef iref)
+{
+    int topIndex = pRef->segmentState.parts.topIndex;
+    int idx = dvmIndirectRefToIndex(iref);
+
+    if (iref == NULL) {
+        LOGI("--- lookup on NULL iref\n");
+        return false;
+    }
+    if (idx >= topIndex) {
+        /* bad -- stale reference? */
+        LOGI("Attempt to access invalid index %d (top=%d)\n",
+            idx, topIndex);
+        return false;
+    }
+
+    Object* obj = pRef->table[idx];
+    if (obj == NULL) {
+        LOGI("Attempt to read from hole, iref=%p\n", iref);
+        return false;
+    }
+    if (!checkEntry(pRef, iref, idx))
+        return false;
+
+    return true;
+}
+
+/*
+ * Remove "obj" from "pRef".  We extract the table offset bits from "iref"
+ * and zap the corresponding entry, leaving a hole if it's not at the top.
+ *
+ * If the entry is not between the current top index and the bottom index
+ * specified by the cookie, we don't remove anything.  This is the behavior
+ * required by JNI's DeleteLocalRef function.
+ *
+ * Returns "false" if nothing was removed.
+ */
+bool dvmRemoveFromIndirectRefTable(IndirectRefTable* pRef, u4 cookie,
+    IndirectRef iref)
+{
+    IRTSegmentState prevState;
+    prevState.all = cookie;
+    int topIndex = pRef->segmentState.parts.topIndex;
+    int bottomIndex = prevState.parts.topIndex;
+
+    assert(pRef->table != NULL);
+    assert(pRef->allocEntries <= pRef->maxEntries);
+    assert(pRef->segmentState.parts.numHoles >= prevState.parts.numHoles);
+
+    int idx = dvmIndirectRefToIndex(iref);
+    if (idx < bottomIndex) {
+        /* wrong segment */
+        LOGV("Attempt to remove index outside index area (%d vs %d-%d)\n",
+            idx, bottomIndex, topIndex);
+        return false;
+    }
+    if (idx >= topIndex) {
+        /* bad -- stale reference? */
+        LOGI("Attempt to remove invalid index %d (bottom=%d top=%d)\n",
+            idx, bottomIndex, topIndex);
+        return false;
+    }
+
+    if (idx == topIndex-1) {
+        /*
+         * Top-most entry.  Scan up and consume holes.  No need to NULL
+         * out the entry, since the test vs. topIndex will catch it.
+         */
+        if (!checkEntry(pRef, iref, idx))
+            return false;
+
+#ifndef NDEBUG
+        pRef->table[idx] = (IndirectRef) 0xd3d3d3d3;
+#endif
+
+        int numHoles =
+            pRef->segmentState.parts.numHoles - prevState.parts.numHoles;
+        if (numHoles != 0) {
+            while (--topIndex > bottomIndex && numHoles != 0) {
+                LOGV("+++ checking for hole at %d (cookie=0x%08x) val=%p\n",
+                    topIndex-1, cookie, pRef->table[topIndex-1]);
+                if (pRef->table[topIndex-1] != NULL)
+                    break;
+                LOGV("+++ ate hole at %d\n", topIndex-1);
+                numHoles--;
+            }
+            pRef->segmentState.parts.numHoles =
+                numHoles + prevState.parts.numHoles;
+            pRef->segmentState.parts.topIndex = topIndex;
+        } else {
+            pRef->segmentState.parts.topIndex = topIndex-1;
+            LOGV("+++ ate last entry %d\n", topIndex-1);
+        }
+    } else {
+        /*
+         * Not the top-most entry.  This creates a hole.  We NULL out the
+         * entry to prevent somebody from deleting it twice and screwing up
+         * the hole count.
+         */
+        if (pRef->table[idx] == NULL) {
+            LOGV("--- WEIRD: removing null entry %d\n", idx);
+            return false;
+        }
+        if (!checkEntry(pRef, iref, idx))
+            return false;
+
+        pRef->table[idx] = NULL;
+        pRef->segmentState.parts.numHoles++;
+        LOGV("+++ left hole at %d, holes=%d\n",
+            idx, pRef->segmentState.parts.numHoles);
+    }
+
+    return true;
+}
+
+/*
+ * This is a qsort() callback.  We sort Object* by class, allocation size,
+ * and then by the Object* itself.
+ */
+static int compareObject(const void* vobj1, const void* vobj2)
+{
+    Object* obj1 = *((Object**) vobj1);
+    Object* obj2 = *((Object**) vobj2);
+
+    /* ensure null references appear at the end */
+    if (obj1 == NULL) {
+        if (obj2 == NULL) {
+            return 0;
+        } else {
+            return 1;
+        }
+    } else if (obj2 == NULL) {
+        return -1;
+    }
+
+    if (obj1->clazz != obj2->clazz) {
+        return (u1*)obj1->clazz - (u1*)obj2->clazz;
+    } else {
+        int size1 = dvmObjectSizeInHeap(obj1);
+        int size2 = dvmObjectSizeInHeap(obj2);
+        if (size1 != size2) {
+            return size1 - size2;
+        } else {
+            return (u1*)obj1 - (u1*)obj2;
+        }
+    }
+}
+
+/*
+ * Log an object with some additional info.
+ *
+ * Pass in the number of additional elements that are identical to or
+ * equivalent to the original.
+ */
+static void logObject(Object* obj, int size, int identical, int equiv)
+{
+    if (obj == NULL) {
+        LOGW("  NULL reference (count=%d)\n", equiv);
+        return;
+    }
+
+    if (identical + equiv != 0) {
+        LOGW("%5d of %s %dB (%d unique)\n", identical + equiv +1,
+            obj->clazz->descriptor, size, equiv +1);
+    } else {
+        LOGW("%5d of %s %dB\n", identical + equiv +1,
+            obj->clazz->descriptor, size);
+    }
+}
+
+/*
+ * Dump the contents of a IndirectRefTable to the log.
+ */
+void dvmDumpIndirectRefTable(const IndirectRefTable* pRef, const char* descr)
+{
+    const int kLast = 10;
+    int count = dvmIndirectRefTableEntries(pRef);
+    Object** refs;
+    int i;
+
+    if (count == 0) {
+        LOGW("Reference table has no entries\n");
+        return;
+    }
+    assert(count > 0);
+
+    /*
+     * Dump the most recent N entries.  If there are holes, we will show
+     * fewer than N.
+     */
+    LOGW("Last %d entries in %s reference table:\n", kLast, descr);
+    refs = pRef->table;         // use unsorted list
+    int size;
+    int start = count - kLast;
+    if (start < 0)
+        start = 0;
+
+    for (i = start; i < count; i++) {
+        if (refs[i] == NULL)
+            continue;
+        size = dvmObjectSizeInHeap(refs[i]);
+        Object* ref = refs[i];
+        if (ref->clazz == gDvm.classJavaLangClass) {
+            ClassObject* clazz = (ClassObject*) ref;
+            LOGW("%5d: %p cls=%s '%s' (%d bytes)\n", i, ref,
+                (refs[i] == NULL) ? "-" : ref->clazz->descriptor,
+                clazz->descriptor, size);
+        } else {
+            LOGW("%5d: %p cls=%s (%d bytes)\n", i, ref,
+                (refs[i] == NULL) ? "-" : ref->clazz->descriptor, size);
+        }
+    }
+
+    /*
+     * Make a copy of the table, and sort it.
+     *
+     * The NULL "holes" wind up at the end, so we can strip them off easily.
+     */
+    Object** tableCopy = (Object**)malloc(sizeof(Object*) * count);
+    memcpy(tableCopy, pRef->table, sizeof(Object*) * count);
+    qsort(tableCopy, count, sizeof(Object*), compareObject);
+    refs = tableCopy;       // use sorted list
+
+    {
+        int q;
+        for (q = 0; q < count; q++)
+            LOGI("%d %p\n", q, refs[q]);
+    }
+
+    int holes = 0;
+    while (refs[count-1] == NULL) {
+        count--;
+        holes++;
+    }
+
+    /*
+     * Dump uniquified table summary.  While we're at it, generate a
+     * cumulative total amount of pinned memory based on the unique entries.
+     */
+    LOGW("%s reference table summary (%d entries / %d holes):\n",
+        descr, count, holes);
+    int equiv, identical, total;
+    total = equiv = identical = 0;
+    for (i = 1; i < count; i++) {
+        size = dvmObjectSizeInHeap(refs[i-1]);
+
+        if (refs[i] == refs[i-1]) {
+            /* same reference, added more than once */
+            identical++;
+        } else if (refs[i]->clazz == refs[i-1]->clazz &&
+            (int) dvmObjectSizeInHeap(refs[i]) == size)
+        {
+            /* same class / size, different object */
+            total += size;
+            equiv++;
+        } else {
+            /* different class */
+            total += size;
+            logObject(refs[i-1], size, identical, equiv);
+            equiv = identical = 0;
+        }
+    }
+
+    /* handle the last entry (everything above outputs refs[i-1]) */
+    size = (refs[count-1] == NULL) ? 0 : dvmObjectSizeInHeap(refs[count-1]);
+    total += size;
+    logObject(refs[count-1], size, identical, equiv);
+
+    LOGW("Memory held directly by native code is %d bytes\n", total);
+    free(tableCopy);
+}
+
diff --git a/vm/IndirectRefTable.h b/vm/IndirectRefTable.h
new file mode 100644
index 0000000..4c842f2
--- /dev/null
+++ b/vm/IndirectRefTable.h
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2009 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.
+ */
+
+#ifndef _DALVIK_INDIRECTREFTABLE
+#define _DALVIK_INDIRECTREFTABLE
+/*
+ * Maintain a table of indirect references.  Used for local/global JNI
+ * references.
+ *
+ * The table contains object references that are part of the GC root set.
+ * When an object is added we return an IndirectRef that is not a valid
+ * pointer but can be used to find the original value in O(1) time.
+ * Conversions to and from indirect refs are performed on JNI method calls
+ * in and out of the VM, so they need to be very fast.
+ *
+ * To be efficient for JNI local variable storage, we need to provide
+ * operations that allow us to operate on segments of the table, where
+ * segments are pushed and popped as if on a stack.  For example, deletion
+ * of an entry should only succeed if it appears in the current segment,
+ * and we want to be able to strip off the current segment quickly when
+ * a method returns.  Additions to the table must be made in the current
+ * segment even if space is available in an earlier area.
+ *
+ * A new segment is created when we call into native code from interpreted
+ * code, or when we handle the JNI PushLocalFrame function.
+ *
+ * The GC must be able to scan the entire table quickly.
+ *
+ * In summary, these must be very fast:
+ *  - adding or removing a segment
+ *  - adding references to a new segment
+ *  - converting an indirect reference back to an Object
+ * These can be a little slower, but must still be pretty quick:
+ *  - adding references to a "mature" segment
+ *  - removing individual references
+ *  - scanning the entire table straight through
+ *
+ * If there's more than one segment, we don't guarantee that the table
+ * will fill completely before we fail due to lack of space.  We do ensure
+ * that the current segment will pack tightly, which should satisfy JNI
+ * requirements (e.g. EnsureLocalCapacity).
+ *
+ * To make everything fit nicely in 32-bit integers, the maximum size of
+ * the table is capped at 64K.
+ *
+ * None of the table functions are synchronized.
+ */
+
+/*
+ * Indirect reference definition.  This must be interchangeable with JNI's
+ * jobject, and it's convenient to let null be null, so we use void*.
+ *
+ * We need a 16-bit table index and a 2-bit reference type (global, local,
+ * weak global).  Real object pointers will have zeroes in the low 2 or 3
+ * bits (4- or 8-byte alignment), so it's useful to put the ref type
+ * in the low bits and reserve zero as an invalid value.
+ *
+ * The remaining 14 bits can be used to detect stale indirect references.
+ * For example, if objects don't move, we can use a hash of the original
+ * Object* to make sure the entry hasn't been re-used.  (If the Object*
+ * we find there doesn't match because of heap movement, we could do a
+ * secondary check on the preserved hash value; this implies that creating
+ * a global/local ref queries the hash value and forces it to be saved.)
+ * This is only done when CheckJNI is enabled.
+ *
+ * A more rigorous approach would be to put a serial number in the extra
+ * bits, and keep a copy of the serial number in a parallel table.  This is
+ * easier when objects can move, but requires 2x the memory and additional
+ * memory accesses on add/get.  It will catch additional problems, e.g.:
+ * create iref1 for obj, delete iref1, create iref2 for same obj, lookup
+ * iref1.  A pattern based on object bits will miss this.
+ */
+typedef void* IndirectRef;
+
+/*
+ * Indirect reference kind, used as the two low bits of IndirectRef.
+ *
+ * For convenience these match up with enum jobjectRefType from jni.h.
+ */
+typedef enum IndirectRefKind {
+    kIndirectKindInvalid    = 0,
+    kIndirectKindLocal      = 1,
+    kIndirectKindGlobal     = 2,
+    kIndirectKindWeakGlobal = 3
+} IndirectRefKind;
+
+/*
+ * Table definition.
+ *
+ * For the global reference table, the expected common operations are
+ * adding a new entry and removing a recently-added entry (usually the
+ * most-recently-added entry).  For JNI local references, the common
+ * operations are adding a new entry and removing an entire table segment.
+ *
+ * If "allocEntries" is not equal to "maxEntries", the table may expand
+ * when entries are added, which means the memory may move.  If you want
+ * to keep pointers into "table" rather than offsets, you must use a
+ * fixed-size table.
+ *
+ * If we delete entries from the middle of the list, we will be left with
+ * "holes".  We track the number of holes so that, when adding new elements,
+ * we can quickly decide to do a trivial append or go slot-hunting.
+ *
+ * When the top-most entry is removed, any holes immediately below it are
+ * also removed.  Thus, deletion of an entry may reduce "topIndex" by more
+ * than one.
+ *
+ * To get the desired behavior for JNI locals, we need to know the bottom
+ * and top of the current "segment".  The top is managed internally, and
+ * the bottom is passed in as a function argument (the VM keeps it in a
+ * slot in the interpreted stack frame).  When we call a native method or
+ * push a local frame, the current top index gets pushed on, and serves
+ * as the new bottom.  When we pop a frame off, the value from the stack
+ * becomes the new top index, and the value stored in the previous frame
+ * becomes the new bottom.
+ *
+ * To avoid having to re-scan the table after a pop, we want to push the
+ * number of holes in the table onto the stack.  Because of our 64K-entry
+ * cap, we can combine the two into a single unsigned 32-bit value.
+ * Instead of a "bottom" argument we take a "cookie", which includes the
+ * bottom index and the count of holes below the bottom.
+ *
+ * We need to minimize method call/return overhead.  If we store the
+ * "cookie" externally, on the interpreted call stack, the VM can handle
+ * pushes and pops with a single 4-byte load and store.  (We could also
+ * store it internally in a public structure, but the local JNI refs are
+ * logically tied to interpreted stack frames anyway.)
+ *
+ * TODO: consider a "lastDeleteIndex" for quick hole-filling when an
+ * add immediately follows a delete; must invalidate after segment pop
+ * (which could increase the cost/complexity of method call/return).
+ * Might be worth only using it for JNI globals.
+ *
+ * TODO: may want completely different add/remove algorithms for global
+ * and local refs to improve performance.  A large circular buffer might
+ * reduce the amortized cost of adding global references.
+ */
+typedef union IRTSegmentState {
+    u4          all;
+    struct {
+        u4      topIndex:16;            /* index of first unused entry */
+        u4      numHoles:16;            /* #of holes in entire table */
+    } parts;
+} IRTSegmentState;
+typedef struct IndirectRefTable {
+    /* semi-public - read/write by interpreter in native call handler */
+    IRTSegmentState segmentState;
+
+    /* semi-public - read-only during GC scan; pointer must not be kept */
+    Object**        table;              /* bottom of the stack */
+
+    /* private */
+    int             allocEntries;       /* #of entries we have space for */
+    int             maxEntries;         /* max #of entries allowed */
+    IndirectRefKind kind;               /* bit mask, ORed into all irefs */
+
+    // TODO: want hole-filling stats (#of holes filled, total entries scanned)
+    //       for performance evaluation.
+} IndirectRefTable;
+
+/* initial value to use for the "cookie" */
+#define IRT_SEGMENT_INIT    0
+
+/*
+ * (This is PRIVATE, but we want it inside other inlines in this header.)
+ *
+ * Indirectify the object.
+ *
+ * The object pointer itself is subject to relocation in some GC
+ * implementations, so we shouldn't really be using it here.
+ */
+INLINE IndirectRef dvmObjectToIndirectRef(Object* obj, u4 tableIndex,
+    IndirectRefKind kind)
+{
+    assert(tableIndex < 65536);
+    u4 objChunk = (((u4) obj >> 3) ^ ((u4) obj >> 19)) & 0x3fff;
+    u4 uref = objChunk << 18 | (tableIndex << 2) | kind;
+    return (IndirectRef) uref;
+}
+
+/*
+ * (This is PRIVATE, but we want it inside other inlines in this header.)
+ *
+ * Extract the table index from an indirect reference.
+ */
+INLINE u4 dvmIndirectRefToIndex(IndirectRef iref)
+{
+    u4 uref = (u4) iref;
+    return (uref >> 2) & 0xffff;
+}
+
+/*
+ * Initialize an IndirectRefTable.
+ *
+ * If "initialCount" != "maxCount", the table will expand as required.
+ *
+ * "kind" should be Local or Global.  The Global table may also hold
+ * WeakGlobal refs.
+ *
+ * Returns "false" if table allocation fails.
+ */
+bool dvmInitIndirectRefTable(IndirectRefTable* pRef, int initialCount,
+    int maxCount, IndirectRefKind kind);
+
+/*
+ * Clear out the contents, freeing allocated storage.  Does not free "pRef".
+ *
+ * You must call dvmInitReferenceTable() before you can re-use this table.
+ */
+void dvmClearIndirectRefTable(IndirectRefTable* pRef);
+
+/*
+ * Start a new segment at the top of the table.
+ *
+ * Returns an opaque 32-bit value that must be provided when the segment
+ * is to be removed.
+ *
+ * IMPORTANT: this is implemented as a single instruction in mterp, rather
+ * than a call here.  You can add debugging aids for the C-language
+ * interpreters, but the basic implementation may not change.
+ */
+INLINE u4 dvmPushIndirectRefTableSegment(IndirectRefTable* pRef)
+{
+    return pRef->segmentState.all;
+}
+
+/* extra debugging checks */
+bool dvmPopIndirectRefTableSegmentCheck(IndirectRefTable* pRef, u4 cookie);
+
+/*
+ * Remove one or more segments from the top.  The table entry identified
+ * by "cookie" becomes the new top-most entry.
+ *
+ * IMPORTANT: this is implemented as a single instruction in mterp, rather
+ * than a call here.  You can add debugging aids for the C-language
+ * interpreters, but the basic implementation may not change.
+ */
+INLINE void dvmPopIndirectRefTableSegment(IndirectRefTable* pRef, u4 cookie)
+{
+    dvmPopIndirectRefTableSegmentCheck(pRef, cookie);
+    pRef->segmentState.all = cookie;
+}
+
+/*
+ * Return the #of entries in the entire table.  This includes holes, and
+ * so may be larger than the actual number of "live" entries.
+ */
+INLINE size_t dvmIndirectRefTableEntries(const IndirectRefTable* pRef)
+{
+    return pRef->segmentState.parts.topIndex;
+}
+
+/*
+ * Returns "true" if the table is full.  The table is considered full if
+ * we would need to expand it to add another entry to the current segment.
+ */
+INLINE size_t dvmIsIndirectRefTableFull(const IndirectRefTable* pRef)
+{
+    return dvmIndirectRefTableEntries(pRef) == (size_t)pRef->allocEntries;
+}
+
+/*
+ * Add a new entry.  "obj" must be a valid non-NULL object reference
+ * (though it's okay if it's not fully-formed, e.g. the result from
+ * dvmMalloc doesn't have obj->clazz set).
+ *
+ * Returns NULL if the table is full (max entries reached, or alloc
+ * failed during expansion).
+ */
+IndirectRef dvmAddToIndirectRefTable(IndirectRefTable* pRef, u4 cookie,
+    Object* obj);
+
+/*
+ * Add a new entry at the end.  Similar to Add but does not usually attempt
+ * to fill in holes.  This is only appropriate to use right after a new
+ * segment has been pushed.
+ *
+ * (This is intended for use when calling into a native JNI method, so
+ * performance is critical.)
+ */
+INLINE IndirectRef dvmAppendToIndirectRefTable(IndirectRefTable* pRef,
+    u4 cookie, Object* obj)
+{
+    int topIndex = pRef->segmentState.parts.topIndex;
+    if (topIndex == pRef->allocEntries) {
+        /* up against alloc or max limit, call the fancy version */
+        return dvmAddToIndirectRefTable(pRef, cookie, obj);
+    } else {
+        IndirectRef result = dvmObjectToIndirectRef(obj, topIndex, pRef->kind);
+        pRef->table[topIndex++] = obj;
+        pRef->segmentState.parts.topIndex = topIndex;
+        return result;
+    }
+}
+
+/* extra debugging checks */
+bool dvmGetFromIndirectRefTableCheck(IndirectRefTable* pRef, IndirectRef iref);
+
+/*
+ * Given an IndirectRef in the table, return the Object it refers to.
+ *
+ * Returns NULL if iref is invalid.
+ */
+INLINE Object* dvmGetFromIndirectRefTable(IndirectRefTable* pRef,
+    IndirectRef iref)
+{
+    if (!dvmGetFromIndirectRefTableCheck(pRef, iref))
+        return NULL;
+
+    int idx = dvmIndirectRefToIndex(iref);
+    return pRef->table[idx];
+}
+
+/*
+ * Remove an existing entry.
+ *
+ * If the entry is not between the current top index and the bottom index
+ * specified by the cookie, we don't remove anything.  This is the behavior
+ * required by JNI's DeleteLocalRef function.
+ *
+ * Returns "false" if nothing was removed.
+ */
+bool dvmRemoveFromIndirectRefTable(IndirectRefTable* pRef, u4 cookie,
+    IndirectRef iref);
+
+/*
+ * Dump the contents of a reference table to the log file.
+ */
+void dvmDumpIndirectRefTable(const IndirectRefTable* pRef, const char* descr);
+
+#endif /*_DALVIK_INDIRECTREFTABLE*/
diff --git a/vm/Init.c b/vm/Init.c
index f546dab..4a2033e 100644
--- a/vm/Init.c
+++ b/vm/Init.c
@@ -1241,7 +1241,10 @@
 
 
 #ifndef NDEBUG
-    dvmTestHash();
+    if (!dvmTestHash())
+        LOGE("dmvTestHash FAILED\n");
+    if (false /*noisy!*/ && !dvmTestIndirectRefTable())
+        LOGE("dvmTestIndirectRefTable FAILED\n");
 #endif
 
     assert(!dvmCheckException(dvmThreadSelf()));
diff --git a/vm/JarFile.c b/vm/JarFile.c
index 4a4eb62..0a58390 100644
--- a/vm/JarFile.c
+++ b/vm/JarFile.c
@@ -261,7 +261,8 @@
                     dexGetZipEntryCrc32(&archive, entry),
                     isBootstrap, &newFile, /*createIfMissing=*/true);
             if (fd < 0) {
-                LOGI("Unable to open or create cache for %s\n", fileName);
+                LOGI("Unable to open or create cache for %s (%s)\n",
+                    fileName, cachedName);
                 goto bail;
             }
             locked = true;
diff --git a/vm/ReferenceTable.c b/vm/ReferenceTable.c
index c748222..089f2f2 100644
--- a/vm/ReferenceTable.c
+++ b/vm/ReferenceTable.c
@@ -58,11 +58,15 @@
     assert(dvmIsValidObject(obj));
     assert(obj != NULL);
     assert(pRef->table != NULL);
+    assert(pRef->allocEntries <= pRef->maxEntries);
 
-    if (pRef->nextEntry == pRef->table + pRef->maxEntries) {
-        LOGW("ReferenceTable overflow (max=%d)\n", pRef->maxEntries);
-        return false;
-    } else if (pRef->nextEntry == pRef->table + pRef->allocEntries) {
+    if (pRef->nextEntry == pRef->table + pRef->allocEntries) {
+        /* reached end of allocated space; did we hit buffer max? */
+        if (pRef->nextEntry == pRef->table + pRef->maxEntries) {
+            LOGW("ReferenceTable overflow (max=%d)\n", pRef->maxEntries);
+            return false;
+        }
+
         Object** newTable;
         int newSize;
 
diff --git a/vm/Thread.h b/vm/Thread.h
index f1e5651..45018b4 100644
--- a/vm/Thread.h
+++ b/vm/Thread.h
@@ -151,6 +151,7 @@
     ReferenceTable  internalLocalRefTable;
 
     /* JNI local reference tracking */
+    // TODO: move this to JNIEnvExt to avoid an indirection?
     ReferenceTable  jniLocalRefTable;
 
     /* JNI native monitor reference tracking (initialized on first use) */
diff --git a/vm/analysis/RegisterMap.c b/vm/analysis/RegisterMap.c
index 20272f2..ad843d0 100644
--- a/vm/analysis/RegisterMap.c
+++ b/vm/analysis/RegisterMap.c
@@ -923,7 +923,7 @@
      * large.
      */
     static const int kSearchThreshold = 8;
-    const u1* data;
+    const u1* data = NULL;
     int lineAddr;
 
     if (numEntries < kSearchThreshold) {
diff --git a/vm/native/dalvik_system_VMDebug.c b/vm/native/dalvik_system_VMDebug.c
index d0fb61d..8aa371d 100644
--- a/vm/native/dalvik_system_VMDebug.c
+++ b/vm/native/dalvik_system_VMDebug.c
@@ -698,13 +698,31 @@
     RETURN_BOOLEAN(result);
 }
 
+/*
+ * static void crash()
+ *
+ * Dump the current thread's interpreted stack and abort the VM.  Useful
+ * for seeing both interpreted and native stack traces.
+ *
+ * (Might want to restrict this to debuggable processes as a security
+ * measure, or check SecurityManager.checkExit().)
+ */
+static void Dalvik_dalvik_system_VMDebug_crash(const u4* args,
+    JValue* pResult)
+{
+    UNUSED_PARAMETER(args);
+    UNUSED_PARAMETER(pResult);
+
+    LOGW("Crashing VM on request\n");
+    dvmDumpThread(dvmThreadSelf(), false);
+    dvmAbort();
+}
+
 const DalvikNativeMethod dvm_dalvik_system_VMDebug[] = {
     { "getAllocCount",              "(I)I",
         Dalvik_dalvik_system_VMDebug_getAllocCount },
     { "resetAllocCount",            "(I)V",
         Dalvik_dalvik_system_VMDebug_resetAllocCount },
-    //{ "print",              "(Ljava/lang/String;)V",
-    //    Dalvik_dalvik_system_VMDebug_print },
     { "startAllocCounting",         "()V",
         Dalvik_dalvik_system_VMDebug_startAllocCounting },
     { "stopAllocCounting",          "()V",
@@ -747,6 +765,8 @@
         Dalvik_dalvik_system_VMDebug_dumpHprofData },
     { "cacheRegisterMap",           "(Ljava/lang/String;)Z",
         Dalvik_dalvik_system_VMDebug_cacheRegisterMap },
+    { "crash",                      "()V",
+        Dalvik_dalvik_system_VMDebug_crash },
     { NULL, NULL, NULL },
 };
 
diff --git a/vm/test/Test.h b/vm/test/Test.h
index ce47aae..f17526f 100644
--- a/vm/test/Test.h
+++ b/vm/test/Test.h
@@ -22,5 +22,6 @@
 
 bool dvmTestHash(void);
 bool dvmTestAtomicSpeed(void);
+bool dvmTestIndirectRefTable(void);
 
 #endif /*_DALVIK_TEST_TEST*/
diff --git a/vm/test/TestHash.c b/vm/test/TestHash.c
index 42fe014..7233b15 100644
--- a/vm/test/TestHash.c
+++ b/vm/test/TestHash.c
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 /*
  * Test the hash table functions.
  */
@@ -20,6 +21,8 @@
 
 #include <stdlib.h>
 
+#ifndef NDEBUG
+
 #define kNumTestEntries 14
 
 /*
@@ -185,3 +188,4 @@
     return true;
 }
 
+#endif /*NDEBUG*/
diff --git a/vm/test/TestIndirectRefTable.c b/vm/test/TestIndirectRefTable.c
new file mode 100644
index 0000000..72f8f33
--- /dev/null
+++ b/vm/test/TestIndirectRefTable.c
@@ -0,0 +1,485 @@
+/*
+ * Copyright (C) 2009 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.
+ */
+
+/*
+ * Test the indirect reference table implementation.
+ */
+#include "Dalvik.h"
+
+#include <stdlib.h>
+
+#ifndef NDEBUG
+
+#define DBUG_MSG    LOGV
+
+/*
+ * Basic add/get/delete tests in an unsegmented table.
+ */
+static bool basicTest(void)
+{
+    static const int kTableMax = 20;
+    IndirectRefTable irt;
+    IndirectRef iref0, iref1, iref2, iref3;
+    IndirectRef manyRefs[kTableMax];
+    ClassObject* clazz = dvmFindClass("Ljava/lang/Object;", NULL);
+    Object* obj0 = dvmAllocObject(clazz, ALLOC_DONT_TRACK);
+    Object* obj1 = dvmAllocObject(clazz, ALLOC_DONT_TRACK);
+    Object* obj2 = dvmAllocObject(clazz, ALLOC_DONT_TRACK);
+    Object* obj3 = dvmAllocObject(clazz, ALLOC_DONT_TRACK);
+    const u4 cookie = IRT_SEGMENT_INIT;
+    bool result = false;
+
+    if (!dvmInitIndirectRefTable(&irt, kTableMax/2, kTableMax,
+            kIndirectKindGlobal))
+    {
+        return false;
+    }
+
+    iref0 = (IndirectRef) 0x11110;
+    if (dvmRemoveFromIndirectRefTable(&irt, cookie, iref0)) {
+        LOGE("unexpectedly successful removal\n");
+        goto bail;
+    }
+
+    /*
+     * Add three, check, remove in the order in which they were added.
+     */
+    DBUG_MSG("+++ START fifo\n");
+    iref0 = dvmAddToIndirectRefTable(&irt, cookie, obj0);
+    iref1 = dvmAddToIndirectRefTable(&irt, cookie, obj1);
+    iref2 = dvmAddToIndirectRefTable(&irt, cookie, obj2);
+    if (iref0 == NULL || iref1 == NULL || iref2 == NULL) {
+        LOGE("trivial add1 failed\n");
+        goto bail;
+    }
+
+    if (dvmGetFromIndirectRefTable(&irt, iref0) != obj0 ||
+        dvmGetFromIndirectRefTable(&irt, iref1) != obj1 ||
+        dvmGetFromIndirectRefTable(&irt, iref2) != obj2)
+    {
+        LOGE("objects don't match expected values %p %p %p vs. %p %p %p\n",
+            dvmGetFromIndirectRefTable(&irt, iref0),
+            dvmGetFromIndirectRefTable(&irt, iref1),
+            dvmGetFromIndirectRefTable(&irt, iref2),
+            obj0, obj1, obj2);
+        goto bail;
+    } else {
+        DBUG_MSG("+++ obj1=%p --> iref1=%p\n", obj1, iref1);
+    }
+
+    if (!dvmRemoveFromIndirectRefTable(&irt, cookie, iref0) ||
+        !dvmRemoveFromIndirectRefTable(&irt, cookie, iref1) ||
+        !dvmRemoveFromIndirectRefTable(&irt, cookie, iref2))
+    {
+        LOGE("fifo deletion failed\n");
+        goto bail;
+    }
+
+    /* table should be empty now */
+    if (dvmIndirectRefTableEntries(&irt) != 0) {
+        LOGE("fifo del not empty\n");
+        goto bail;
+    }
+
+    /* get invalid entry (off the end of the list) */
+    if (dvmGetFromIndirectRefTable(&irt, iref0) != NULL) {
+        LOGE("stale entry get succeeded unexpectedly\n");
+        goto bail;
+    }
+
+    /*
+     * Add three, remove in the opposite order.
+     */
+    DBUG_MSG("+++ START lifo\n");
+    iref0 = dvmAddToIndirectRefTable(&irt, cookie, obj0);
+    iref1 = dvmAddToIndirectRefTable(&irt, cookie, obj1);
+    iref2 = dvmAddToIndirectRefTable(&irt, cookie, obj2);
+    if (iref0 == NULL || iref1 == NULL || iref2 == NULL) {
+        LOGE("trivial add2 failed\n");
+        goto bail;
+    }
+
+    if (!dvmRemoveFromIndirectRefTable(&irt, cookie, iref2) ||
+        !dvmRemoveFromIndirectRefTable(&irt, cookie, iref1) ||
+        !dvmRemoveFromIndirectRefTable(&irt, cookie, iref0))
+    {
+        LOGE("lifo deletion failed\n");
+        goto bail;
+    }
+
+    /* table should be empty now */
+    if (dvmIndirectRefTableEntries(&irt) != 0) {
+        LOGE("lifo del not empty\n");
+        goto bail;
+    }
+
+    /*
+     * Add three, remove middle / middle / bottom / top.  (Second attempt
+     * to remove middle should fail.)
+     */
+    DBUG_MSG("+++ START unorder\n");
+    iref0 = dvmAddToIndirectRefTable(&irt, cookie, obj0);
+    iref1 = dvmAddToIndirectRefTable(&irt, cookie, obj1);
+    iref2 = dvmAddToIndirectRefTable(&irt, cookie, obj2);
+    if (iref0 == NULL || iref1 == NULL || iref2 == NULL) {
+        LOGE("trivial add3 failed\n");
+        goto bail;
+    }
+
+    if (dvmIndirectRefTableEntries(&irt) != 3) {
+        LOGE("expected 3 entries, found %d\n",
+            dvmIndirectRefTableEntries(&irt));
+        goto bail;
+    }
+
+    if (!dvmRemoveFromIndirectRefTable(&irt, cookie, iref1) ||
+        dvmRemoveFromIndirectRefTable(&irt, cookie, iref1))
+    {
+        LOGE("unorder deletion1 failed\n");
+        goto bail;
+    }
+
+    /* get invalid entry (from hole) */
+    if (dvmGetFromIndirectRefTable(&irt, iref1) != NULL) {
+        LOGE("hole get succeeded unexpectedly\n");
+        goto bail;
+    }
+
+    if (!dvmRemoveFromIndirectRefTable(&irt, cookie, iref2) ||
+        !dvmRemoveFromIndirectRefTable(&irt, cookie, iref0))
+    {
+        LOGE("unorder deletion2 failed\n");
+        goto bail;
+    }
+
+    /* table should be empty now */
+    if (dvmIndirectRefTableEntries(&irt) != 0) {
+        LOGE("unorder del not empty\n");
+        goto bail;
+    }
+
+    /*
+     * Add four entries.  Remove #1, add new entry, verify that table size
+     * is still 4 (i.e. holes are getting filled).  Remove #1 and #3, verify
+     * that we delete one and don't hole-compact the other.
+     */
+    DBUG_MSG("+++ START hole fill\n");
+    iref0 = dvmAddToIndirectRefTable(&irt, cookie, obj0);
+    iref1 = dvmAddToIndirectRefTable(&irt, cookie, obj1);
+    iref2 = dvmAddToIndirectRefTable(&irt, cookie, obj2);
+    iref3 = dvmAddToIndirectRefTable(&irt, cookie, obj3);
+    if (iref0 == NULL || iref1 == NULL || iref2 == NULL || iref3 == NULL) {
+        LOGE("trivial add4 failed\n");
+        goto bail;
+    }
+    if (!dvmRemoveFromIndirectRefTable(&irt, cookie, iref1)) {
+        LOGE("remove 1 of 4 failed\n");
+        goto bail;
+    }
+    iref1 = dvmAddToIndirectRefTable(&irt, cookie, obj1);
+    if (dvmIndirectRefTableEntries(&irt) != 4) {
+        LOGE("hole not filled\n");
+        goto bail;
+    }
+    if (!dvmRemoveFromIndirectRefTable(&irt, cookie, iref1) ||
+        !dvmRemoveFromIndirectRefTable(&irt, cookie, iref3))
+    {
+        LOGE("remove 1/3 failed\n");
+        goto bail;
+    }
+    if (dvmIndirectRefTableEntries(&irt) != 3) {
+        LOGE("should be 3 after two deletions\n");
+        goto bail;
+    }
+    if (!dvmRemoveFromIndirectRefTable(&irt, cookie, iref2) ||
+        !dvmRemoveFromIndirectRefTable(&irt, cookie, iref0))
+    {
+        LOGE("remove 2/0 failed\n");
+        goto bail;
+    }
+    if (dvmIndirectRefTableEntries(&irt) != 0) {
+        LOGE("not empty after split remove\n");
+        goto bail;
+    }
+
+    /*
+     * Add an entry, remove it, add a new entry, and try to use the original
+     * iref.  They have the same slot number but are for different objects.
+     * With the extended checks in place, this should fail.
+     */
+    DBUG_MSG("+++ START switched\n");
+    iref0 = dvmAddToIndirectRefTable(&irt, cookie, obj0);
+    dvmRemoveFromIndirectRefTable(&irt, cookie, iref0);
+    iref1 = dvmAddToIndirectRefTable(&irt, cookie, obj1);
+    if (dvmRemoveFromIndirectRefTable(&irt, cookie, iref0)) {
+        LOGE("mismatched del succeeded (%p vs %p)\n", iref0, iref1);
+        goto bail;
+    }
+    if (!dvmRemoveFromIndirectRefTable(&irt, cookie, iref1)) {
+        LOGE("switched del failed\n");
+        goto bail;
+    }
+    if (dvmIndirectRefTableEntries(&irt) != 0) {
+        LOGE("switching del not empty\n");
+        goto bail;
+    }
+
+    /*
+     * Same as above, but with the same object.  A more rigorous checker
+     * (e.g. with slot serialization) will catch this.
+     */
+    iref0 = dvmAddToIndirectRefTable(&irt, cookie, obj0);
+    dvmRemoveFromIndirectRefTable(&irt, cookie, iref0);
+    iref1 = dvmAddToIndirectRefTable(&irt, cookie, obj0);
+    if (iref0 != iref1) {
+        if (dvmRemoveFromIndirectRefTable(&irt, cookie, iref0)) {
+            LOGE("temporal del succeeded (%p vs %p)\n", iref0, iref1);
+            goto bail;
+        }
+    } else {
+        dvmRemoveFromIndirectRefTable(&irt, cookie, iref1);
+    }
+    if (dvmIndirectRefTableEntries(&irt) != 0) {
+        LOGE("temporal del not empty\n");
+        goto bail;
+    }
+
+    /*
+     * Test table overflow.
+     */
+    DBUG_MSG("+++ START overflow\n");
+    int i;
+    for (i = 0; i < kTableMax; i++) {
+        manyRefs[i] = dvmAddToIndirectRefTable(&irt, cookie, obj0);
+        if (manyRefs[i] == NULL) {
+            LOGE("Failed adding %d of %d\n", i, kTableMax);
+            goto bail;
+        }
+    }
+    if (dvmAddToIndirectRefTable(&irt, cookie, obj0) != NULL) {
+        LOGE("Table overflow succeeded\n");
+        goto bail;
+    }
+    if (dvmIndirectRefTableEntries(&irt) != (size_t)kTableMax) {
+        LOGE("Expected %d entries, found %d\n",
+            kTableMax, dvmIndirectRefTableEntries(&irt));
+        goto bail;
+    }
+    for (i = 0; i < kTableMax-1; i++) {
+        if (!dvmRemoveFromIndirectRefTable(&irt, cookie, manyRefs[i])) {
+            LOGE("multi-remove failed at %d\n", i);
+            goto bail;
+        }
+    }
+    /* because of removal order, should have 20 entries, 19 of them holes */
+    if (dvmIndirectRefTableEntries(&irt) != (size_t)kTableMax) {
+        LOGE("Expected %d entries (with holes), found %d\n",
+            kTableMax, dvmIndirectRefTableEntries(&irt));
+        goto bail;
+    }
+    if (!dvmRemoveFromIndirectRefTable(&irt, cookie, manyRefs[kTableMax-1])) {
+        LOGE("multi-remove final failed\n");
+        goto bail;
+    }
+    if (dvmIndirectRefTableEntries(&irt) != 0) {
+        LOGE("multi-del not empty\n");
+        goto bail;
+    }
+
+    DBUG_MSG("+++ basic test complete\n");
+    result = true;
+
+bail:
+    dvmClearIndirectRefTable(&irt);
+    return result;
+}
+
+/*
+ * Test operations on a segmented table.
+ */
+static bool segmentTest(void)
+{
+    static const int kTableMax = 20;
+    IndirectRefTable irt;
+    IndirectRef iref0, iref1, iref2, iref3;
+    ClassObject* clazz = dvmFindClass("Ljava/lang/Object;", NULL);
+    Object* obj0 = dvmAllocObject(clazz, ALLOC_DONT_TRACK);
+    Object* obj1 = dvmAllocObject(clazz, ALLOC_DONT_TRACK);
+    Object* obj2 = dvmAllocObject(clazz, ALLOC_DONT_TRACK);
+    Object* obj3 = dvmAllocObject(clazz, ALLOC_DONT_TRACK);
+    u4 cookie;
+    u4 segmentState[4];
+    bool result = false;
+
+    if (!dvmInitIndirectRefTable(&irt, kTableMax, kTableMax,
+            kIndirectKindLocal))
+    {
+        return false;
+    }
+    cookie = segmentState[0] = IRT_SEGMENT_INIT;
+    DBUG_MSG("+++ objs %p %p %p %p\n", obj0, obj1, obj2, obj3);
+
+    /*
+     * Push two, create new segment, push two more, try to get all four,
+     * try to delete all 4.  All four should be accessible, but only the
+     * last two should be deletable.
+     */
+    DBUG_MSG("+++ START basic segment\n");
+    iref0 = dvmAddToIndirectRefTable(&irt, cookie, obj0);
+    iref1 = dvmAddToIndirectRefTable(&irt, cookie, obj1);
+    cookie = segmentState[1] = dvmPushIndirectRefTableSegment(&irt);
+    DBUG_MSG("+++ pushed, cookie is 0x%08x\n", cookie);
+    iref2 = dvmAddToIndirectRefTable(&irt, cookie, obj2);
+    iref3 = dvmAddToIndirectRefTable(&irt, cookie, obj3);
+
+    if (dvmRemoveFromIndirectRefTable(&irt, cookie, iref0) ||
+        dvmRemoveFromIndirectRefTable(&irt, cookie, iref1))
+    {
+        LOGE("removed values from earlier segment\n");
+        goto bail;
+    }
+    if (!dvmRemoveFromIndirectRefTable(&irt, cookie, iref2) ||
+        !dvmRemoveFromIndirectRefTable(&irt, cookie, iref3))
+    {
+        LOGE("unable to remove values from current segment\n");
+        goto bail;
+    }
+    if (dvmIndirectRefTableEntries(&irt) != 2) {
+        LOGE("wrong total entries\n");
+        goto bail;
+    }
+    dvmPopIndirectRefTableSegment(&irt, segmentState[1]);
+    cookie = segmentState[0];
+    if (!dvmRemoveFromIndirectRefTable(&irt, cookie, iref0) ||
+        !dvmRemoveFromIndirectRefTable(&irt, cookie, iref1))
+    {
+        LOGE("unable to remove values from first segment\n");
+        goto bail;
+    }
+    if (dvmIndirectRefTableEntries(&irt) != 0) {
+        LOGE("basic push/pop not empty\n");
+        goto bail;
+    }
+
+    /*
+     * Push two, delete first, segment, push two more, pop segment, verify
+     * the last two are no longer present and hole count is right.  The
+     * adds after the segment pop should not be filling in the hole.
+     */
+    DBUG_MSG("+++ START segment pop\n");
+    iref0 = dvmAddToIndirectRefTable(&irt, cookie, obj0);
+    iref1 = dvmAddToIndirectRefTable(&irt, cookie, obj1);
+    dvmRemoveFromIndirectRefTable(&irt, cookie, iref0);
+    cookie = segmentState[1] = dvmPushIndirectRefTableSegment(&irt);
+    iref2 = dvmAddToIndirectRefTable(&irt, cookie, obj2);
+    iref3 = dvmAddToIndirectRefTable(&irt, cookie, obj3);
+    dvmPopIndirectRefTableSegment(&irt, segmentState[1]);
+    cookie = segmentState[0];
+    if (dvmIndirectRefTableEntries(&irt) != 2) {
+        LOGE("wrong total entries after pop\n");
+        goto bail;
+    }
+    dvmRemoveFromIndirectRefTable(&irt, cookie, iref1);
+    if (dvmIndirectRefTableEntries(&irt) != 0) {
+        LOGE("not back to zero after pop + del\n");
+        goto bail;
+    }
+
+    /*
+     * Multiple segments, some empty.
+     */
+    DBUG_MSG("+++ START multiseg\n");
+    iref0 = dvmAppendToIndirectRefTable(&irt, cookie, obj0);
+    iref1 = dvmAppendToIndirectRefTable(&irt, cookie, obj1);
+    cookie = segmentState[1] = dvmPushIndirectRefTableSegment(&irt);
+    cookie = segmentState[2] = dvmPushIndirectRefTableSegment(&irt);
+    iref3 = dvmAppendToIndirectRefTable(&irt, cookie, obj3);
+    iref2 = dvmAppendToIndirectRefTable(&irt, cookie, obj2);
+    dvmRemoveFromIndirectRefTable(&irt, cookie, iref3);
+    cookie = segmentState[3] = dvmPushIndirectRefTableSegment(&irt);
+    iref3 = dvmAppendToIndirectRefTable(&irt, cookie, obj3);
+
+    if (dvmGetFromIndirectRefTable(&irt, iref0) != obj0 ||
+        dvmGetFromIndirectRefTable(&irt, iref1) != obj1 ||
+        dvmGetFromIndirectRefTable(&irt, iref2) != obj2 ||
+        dvmGetFromIndirectRefTable(&irt, iref3) != obj3)
+    {
+        LOGE("Unable to retrieve all multiseg objects\n");
+        goto bail;
+    }
+
+    dvmDumpIndirectRefTable(&irt, "test");
+
+    //int i;
+    //for (i = 0; i < sizeof(segmentState) / sizeof(segmentState[0]); i++) {
+    //    DBUG_MSG("+++  segment %d = 0x%08x\n", i, segmentState[i]);
+    //}
+
+    dvmRemoveFromIndirectRefTable(&irt, cookie, iref3);
+    if (dvmRemoveFromIndirectRefTable(&irt, cookie, iref2)) {
+        LOGE("multiseg del2 worked\n");
+        goto bail;
+    }
+    dvmPopIndirectRefTableSegment(&irt, segmentState[3]);
+    cookie = segmentState[2];
+    if (!dvmRemoveFromIndirectRefTable(&irt, cookie, iref2)) {
+        LOGE("multiseg del2b failed (cookie=0x%08x ref=%p)\n", cookie, iref2);
+        goto bail;
+    }
+    iref2 = dvmAddToIndirectRefTable(&irt, cookie, obj2);
+
+    /* pop two off at once */
+    dvmPopIndirectRefTableSegment(&irt, segmentState[1]);
+    cookie = segmentState[0];
+
+    if (dvmIndirectRefTableEntries(&irt) != 2) {
+        LOGE("Unexpected entry count in multiseg\n");
+        goto bail;
+    }
+    dvmRemoveFromIndirectRefTable(&irt, cookie, iref0);
+    dvmRemoveFromIndirectRefTable(&irt, cookie, iref1);
+    if (dvmIndirectRefTableEntries(&irt) != 0) {
+        LOGE("Unexpected entry count at multiseg end\n");
+        goto bail;
+    }
+
+    DBUG_MSG("+++ segment test complete\n");
+    result = true;
+
+bail:
+    dvmClearIndirectRefTable(&irt);
+    return result;
+}
+
+
+/*
+ * Some quick tests.
+ */
+bool dvmTestIndirectRefTable(void)
+{
+    if (!basicTest()) {
+        LOGE("IRT basic test failed\n");
+        return false;
+    }
+    if (!segmentTest()) {
+        LOGE("IRT segment test failed\n");
+        return false;
+    }
+
+    return true;
+}
+
+#endif /*NDEBUG*/