diff options
-rw-r--r-- | vm/Android.mk | 8 | ||||
-rw-r--r-- | vm/Common.h | 5 | ||||
-rw-r--r-- | vm/Dalvik.h | 1 | ||||
-rw-r--r-- | vm/IndirectRefTable.c | 453 | ||||
-rw-r--r-- | vm/IndirectRefTable.h | 344 | ||||
-rw-r--r-- | vm/Init.c | 5 | ||||
-rw-r--r-- | vm/JarFile.c | 3 | ||||
-rw-r--r-- | vm/ReferenceTable.c | 12 | ||||
-rw-r--r-- | vm/Thread.h | 1 | ||||
-rw-r--r-- | vm/analysis/RegisterMap.c | 2 | ||||
-rw-r--r-- | vm/test/Test.h | 1 | ||||
-rw-r--r-- | vm/test/TestHash.c | 4 | ||||
-rw-r--r-- | vm/test/TestIndirectRefTable.c | 485 |
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*/ @@ -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*/ |