summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--vm/Android.mk8
-rw-r--r--vm/Common.h5
-rw-r--r--vm/Dalvik.h1
-rw-r--r--vm/IndirectRefTable.c453
-rw-r--r--vm/IndirectRefTable.h344
-rw-r--r--vm/Init.c5
-rw-r--r--vm/JarFile.c3
-rw-r--r--vm/ReferenceTable.c12
-rw-r--r--vm/Thread.h1
-rw-r--r--vm/analysis/RegisterMap.c2
-rw-r--r--vm/test/Test.h1
-rw-r--r--vm/test/TestHash.c4
-rw-r--r--vm/test/TestIndirectRefTable.c485
13 files changed, 1313 insertions, 11 deletions
diff --git a/vm/Android.mk b/vm/Android.mk
index 8f873d42b..48b53af98 100644
--- a/vm/Android.mk
+++ b/vm/Android.mk
@@ -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
diff --git a/vm/Common.h b/vm/Common.h
index 8ca522464..4b357e2e1 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 618d51ae9..054b838e0 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 000000000..0106fe468
--- /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 000000000..4c842f222
--- /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 f546dab60..4a2033e76 100644
--- 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()));
diff --git a/vm/JarFile.c b/vm/JarFile.c
index 4a4eb622e..0a58390a5 100644
--- a/vm/JarFile.c
+++ b/vm/JarFile.c
@@ -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;
diff --git a/vm/ReferenceTable.c b/vm/ReferenceTable.c
index c748222a7..089f2f287 100644
--- a/vm/ReferenceTable.c
+++ b/vm/ReferenceTable.c
@@ -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;
diff --git a/vm/Thread.h b/vm/Thread.h
index f1e5651de..45018b4a0 100644
--- a/vm/Thread.h
+++ b/vm/Thread.h
@@ -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) */
diff --git a/vm/analysis/RegisterMap.c b/vm/analysis/RegisterMap.c
index 20272f2cb..ad843d0d2 100644
--- a/vm/analysis/RegisterMap.c
+++ b/vm/analysis/RegisterMap.c
@@ -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) {
diff --git a/vm/test/Test.h b/vm/test/Test.h
index ce47aae07..f17526fcd 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 42fe014d9..7233b15e1 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 @@ bool dvmTestHash(void)
return true;
}
+#endif /*NDEBUG*/
diff --git a/vm/test/TestIndirectRefTable.c b/vm/test/TestIndirectRefTable.c
new file mode 100644
index 000000000..72f8f33b2
--- /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*/