OSDN Git Service

Indirect reference table implementation.
authorAndy McFadden <fadden@android.com>
Fri, 17 Jul 2009 01:11:22 +0000 (18:11 -0700)
committerAndy McFadden <fadden@android.com>
Fri, 31 Jul 2009 20:18:56 +0000 (13:18 -0700)
This change introduces the "indirect" reference table, which will be
replacing ReferenceTable for local and global JNI references.  The key
difference is that, instead of handing raw Object pointers to JNI, we
will be giving them a magic value that can be converted back to an
Object.  The goal is to avoid having to pin every object that native
code is aware of.

The code is not actually used anywhere yet.

Also bundled up here:
 - added detail to a log message
 - fixed a string format issue in the internal assert() definition
 - very minor optimization in "remove" function in ReferenceTable
 - quiet a gcc complaint
 - only include the hash table regression test in builds that invoke it

13 files changed:
vm/Android.mk
vm/Common.h
vm/Dalvik.h
vm/IndirectRefTable.c [new file with mode: 0644]
vm/IndirectRefTable.h [new file with mode: 0644]
vm/Init.c
vm/JarFile.c
vm/ReferenceTable.c
vm/Thread.h
vm/analysis/RegisterMap.c
vm/test/Test.h
vm/test/TestHash.c
vm/test/TestIndirectRefTable.c [new file with mode: 0644]

index 8f873d4..48b53af 100644 (file)
@@ -57,7 +57,7 @@ ifeq ($(strip $(DEBUG_DALVIK_VM)),true)
   # - 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 @@ ifeq ($(strip $(DEBUG_DALVIK_VM)),true)
   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 @@ LOCAL_SRC_FILES := \
        DvmDex.c \
        Exception.c \
        Hash.c \
+       IndirectRefTable.c.arm \
        Init.c \
        InlineNative.c.arm \
        Inlines.c \
@@ -182,7 +185,8 @@ LOCAL_SRC_FILES := \
        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
index 8ca5224..4b357e2 100644 (file)
@@ -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
 
 
index 618d51a..054b838 100644 (file)
@@ -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 (file)
index 0000000..0106fe4
--- /dev/null
@@ -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 (file)
index 0000000..4c842f2
--- /dev/null
@@ -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*/
index f546dab..4a2033e 100644 (file)
--- a/vm/Init.c
+++ b/vm/Init.c
@@ -1241,7 +1241,10 @@ int dvmStartup(int argc, const char* const argv[], bool ignoreUnrecognized,
 
 
 #ifndef NDEBUG
-    dvmTestHash();
+    if (!dvmTestHash())
+        LOGE("dmvTestHash FAILED\n");
+    if (false /*noisy!*/ && !dvmTestIndirectRefTable())
+        LOGE("dvmTestIndirectRefTable FAILED\n");
 #endif
 
     assert(!dvmCheckException(dvmThreadSelf()));
index 4a4eb62..0a58390 100644 (file)
@@ -261,7 +261,8 @@ tryArchive:
                     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;
index c748222..089f2f2 100644 (file)
@@ -58,11 +58,15 @@ bool dvmAddToReferenceTable(ReferenceTable* pRef, Object* obj)
     assert(dvmIsValidObject(obj));
     assert(obj != NULL);
     assert(pRef->table != NULL);
+    assert(pRef->allocEntries <= pRef->maxEntries);
+
+    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;
+        }
 
-    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) {
         Object** newTable;
         int newSize;
 
index f1e5651..45018b4 100644 (file)
@@ -151,6 +151,7 @@ typedef struct Thread {
     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) */
index 20272f2..ad843d0 100644 (file)
@@ -923,7 +923,7 @@ const u1* dvmRegisterMapGetLine(const RegisterMap* pMap, int addr)
      * large.
      */
     static const int kSearchThreshold = 8;
-    const u1* data;
+    const u1* data = NULL;
     int lineAddr;
 
     if (numEntries < kSearchThreshold) {
index ce47aae..f17526f 100644 (file)
@@ -22,5 +22,6 @@
 
 bool dvmTestHash(void);
 bool dvmTestAtomicSpeed(void);
+bool dvmTestIndirectRefTable(void);
 
 #endif /*_DALVIK_TEST_TEST*/
index 42fe014..7233b15 100644 (file)
@@ -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 @@ bool dvmTestHash(void)
     return true;
 }
 
+#endif /*NDEBUG*/
diff --git a/vm/test/TestIndirectRefTable.c b/vm/test/TestIndirectRefTable.c
new file mode 100644 (file)
index 0000000..72f8f33
--- /dev/null
@@ -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*/