/* * 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> #include <sys/time.h> #ifndef NDEBUG #define DBUG_MSG ALOGI class Stopwatch { public: Stopwatch() { reset(); } void reset() { start_ = now(); } float elapsedSeconds() { return (now() - start_) * 0.000001f; } private: u8 start_; static u8 now() { #ifdef HAVE_POSIX_CLOCKS struct timespec tm; clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tm); return tm.tv_sec * 1000000LL + tm.tv_nsec / 1000; #else struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec * 1000000LL + tv.tv_usec; #endif } }; /* * Basic add/get/delete tests in an unsegmented table. */ static bool basicTest() { 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_FIRST_SEGMENT; bool result = false; if (!irt.init(kTableMax/2, kTableMax, kIndirectKindGlobal)) { return false; } iref0 = (IndirectRef) 0x11110; if (irt.remove(cookie, iref0)) { ALOGE("unexpectedly successful removal"); goto bail; } /* * Add three, check, remove in the order in which they were added. */ DBUG_MSG("+++ START fifo\n"); iref0 = irt.add(cookie, obj0); iref1 = irt.add(cookie, obj1); iref2 = irt.add(cookie, obj2); if (iref0 == NULL || iref1 == NULL || iref2 == NULL) { ALOGE("trivial add1 failed"); goto bail; } if (irt.get(iref0) != obj0 || irt.get(iref1) != obj1 || irt.get(iref2) != obj2) { ALOGE("objects don't match expected values %p %p %p vs. %p %p %p", irt.get(iref0), irt.get(iref1), irt.get(iref2), obj0, obj1, obj2); goto bail; } else { DBUG_MSG("+++ obj1=%p --> iref1=%p\n", obj1, iref1); } if (!irt.remove(cookie, iref0) || !irt.remove(cookie, iref1) || !irt.remove(cookie, iref2)) { ALOGE("fifo deletion failed"); goto bail; } /* table should be empty now */ if (irt.capacity() != 0) { ALOGE("fifo del not empty"); goto bail; } /* get invalid entry (off the end of the list) */ if (irt.get(iref0) != kInvalidIndirectRefObject) { ALOGE("stale entry get succeeded unexpectedly"); goto bail; } /* * Add three, remove in the opposite order. */ DBUG_MSG("+++ START lifo\n"); iref0 = irt.add(cookie, obj0); iref1 = irt.add(cookie, obj1); iref2 = irt.add(cookie, obj2); if (iref0 == NULL || iref1 == NULL || iref2 == NULL) { ALOGE("trivial add2 failed"); goto bail; } if (!irt.remove(cookie, iref2) || !irt.remove(cookie, iref1) || !irt.remove(cookie, iref0)) { ALOGE("lifo deletion failed"); goto bail; } /* table should be empty now */ if (irt.capacity() != 0) { ALOGE("lifo del not empty"); goto bail; } /* * Add three, remove middle / middle / bottom / top. (Second attempt * to remove middle should fail.) */ DBUG_MSG("+++ START unorder\n"); iref0 = irt.add(cookie, obj0); iref1 = irt.add(cookie, obj1); iref2 = irt.add(cookie, obj2); if (iref0 == NULL || iref1 == NULL || iref2 == NULL) { ALOGE("trivial add3 failed"); goto bail; } if (irt.capacity() != 3) { ALOGE("expected 3 entries, found %d", irt.capacity()); goto bail; } if (!irt.remove(cookie, iref1) || irt.remove(cookie, iref1)) { ALOGE("unorder deletion1 failed"); goto bail; } /* get invalid entry (from hole) */ if (irt.get(iref1) != kInvalidIndirectRefObject) { ALOGE("hole get succeeded unexpectedly"); goto bail; } if (!irt.remove(cookie, iref2) || !irt.remove(cookie, iref0)) { ALOGE("unorder deletion2 failed"); goto bail; } /* table should be empty now */ if (irt.capacity() != 0) { ALOGE("unorder del not empty"); 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 = irt.add(cookie, obj0); iref1 = irt.add(cookie, obj1); iref2 = irt.add(cookie, obj2); iref3 = irt.add(cookie, obj3); if (iref0 == NULL || iref1 == NULL || iref2 == NULL || iref3 == NULL) { ALOGE("trivial add4 failed"); goto bail; } if (!irt.remove(cookie, iref1)) { ALOGE("remove 1 of 4 failed"); goto bail; } iref1 = irt.add(cookie, obj1); if (irt.capacity() != 4) { ALOGE("hole not filled"); goto bail; } if (!irt.remove(cookie, iref1) || !irt.remove(cookie, iref3)) { ALOGE("remove 1/3 failed"); goto bail; } if (irt.capacity() != 3) { ALOGE("should be 3 after two deletions"); goto bail; } if (!irt.remove(cookie, iref2) || !irt.remove(cookie, iref0)) { ALOGE("remove 2/0 failed"); goto bail; } if (irt.capacity() != 0) { ALOGE("not empty after split remove"); 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 = irt.add(cookie, obj0); irt.remove(cookie, iref0); iref1 = irt.add(cookie, obj1); if (irt.remove(cookie, iref0)) { ALOGE("mismatched del succeeded (%p vs %p)", iref0, iref1); goto bail; } if (!irt.remove(cookie, iref1)) { ALOGE("switched del failed"); goto bail; } if (irt.capacity() != 0) { ALOGE("switching del not empty"); goto bail; } /* * Same as above, but with the same object. A more rigorous checker * (e.g. with slot serialization) will catch this. */ DBUG_MSG("+++ START switched same object\n"); iref0 = irt.add(cookie, obj0); irt.remove(cookie, iref0); iref1 = irt.add(cookie, obj0); if (iref0 != iref1) { /* try 0, should not work */ if (irt.remove(cookie, iref0)) { ALOGE("temporal del succeeded (%p vs %p)", iref0, iref1); goto bail; } } if (!irt.remove(cookie, iref1)) { ALOGE("temporal cleanup failed"); goto bail; } if (irt.capacity() != 0) { ALOGE("temporal del not empty"); goto bail; } DBUG_MSG("+++ START null lookup\n"); if (irt.get(NULL) != kInvalidIndirectRefObject) { ALOGE("null lookup succeeded"); goto bail; } DBUG_MSG("+++ START stale lookup\n"); iref0 = irt.add(cookie, obj0); irt.remove(cookie, iref0); if (irt.get(iref0) != kInvalidIndirectRefObject) { ALOGE("stale lookup succeeded"); goto bail; } /* * Test table overflow. */ DBUG_MSG("+++ START overflow\n"); int i; for (i = 0; i < kTableMax; i++) { manyRefs[i] = irt.add(cookie, obj0); if (manyRefs[i] == NULL) { ALOGE("Failed adding %d of %d", i, kTableMax); goto bail; } } if (irt.add(cookie, obj0) != NULL) { ALOGE("Table overflow succeeded"); goto bail; } if (irt.capacity() != (size_t)kTableMax) { ALOGE("Expected %d entries, found %d", kTableMax, irt.capacity()); goto bail; } irt.dump("table with 20 entries, all filled"); for (i = 0; i < kTableMax-1; i++) { if (!irt.remove(cookie, manyRefs[i])) { ALOGE("multi-remove failed at %d", i); goto bail; } } irt.dump("table with 20 entries, 19 of them holes"); /* because of removal order, should have 20 entries, 19 of them holes */ if (irt.capacity() != (size_t)kTableMax) { ALOGE("Expected %d entries (with holes), found %d", kTableMax, irt.capacity()); goto bail; } if (!irt.remove(cookie, manyRefs[kTableMax-1])) { ALOGE("multi-remove final failed"); goto bail; } if (irt.capacity() != 0) { ALOGE("multi-del not empty"); goto bail; } /* Done */ DBUG_MSG("+++ basic test complete\n"); result = true; bail: irt.destroy(); return result; } static bool performanceTest() { static const int kTableMax = 100; IndirectRefTable irt; IndirectRef manyRefs[kTableMax]; ClassObject* clazz = dvmFindClass("Ljava/lang/Object;", NULL); Object* obj0 = dvmAllocObject(clazz, ALLOC_DONT_TRACK); const u4 cookie = IRT_FIRST_SEGMENT; const int kLoops = 100000; Stopwatch stopwatch; DBUG_MSG("+++ START performance\n"); if (!irt.init(kTableMax, kTableMax, kIndirectKindGlobal)) { return false; } stopwatch.reset(); for (int loop = 0; loop < kLoops; loop++) { for (int i = 0; i < kTableMax; i++) { manyRefs[i] = irt.add(cookie, obj0); } for (int i = 0; i < kTableMax; i++) { irt.remove(cookie, manyRefs[i]); } } DBUG_MSG("Add/remove %d objects FIFO order, %d iterations, %0.3fms / iteration", kTableMax, kLoops, stopwatch.elapsedSeconds() * 1000 / kLoops); stopwatch.reset(); for (int loop = 0; loop < kLoops; loop++) { for (int i = 0; i < kTableMax; i++) { manyRefs[i] = irt.add(cookie, obj0); } for (int i = kTableMax; i-- > 0; ) { irt.remove(cookie, manyRefs[i]); } } DBUG_MSG("Add/remove %d objects LIFO order, %d iterations, %0.3fms / iteration", kTableMax, kLoops, stopwatch.elapsedSeconds() * 1000 / kLoops); for (int i = 0; i < kTableMax; i++) { manyRefs[i] = irt.add(cookie, obj0); } stopwatch.reset(); for (int loop = 0; loop < kLoops; loop++) { for (int i = 0; i < kTableMax; i++) { irt.get(manyRefs[i]); } } DBUG_MSG("Get %d objects, %d iterations, %0.3fms / iteration", kTableMax, kLoops, stopwatch.elapsedSeconds() * 1000 / kLoops); for (int i = kTableMax; i-- > 0; ) { irt.remove(cookie, manyRefs[i]); } irt.destroy(); return true; } /* * Some quick tests. */ bool dvmTestIndirectRefTable() { if (!basicTest()) { ALOGE("IRT basic test failed"); return false; } if (!performanceTest()) { ALOGE("IRT performance test failed"); return false; } return true; } #endif /*NDEBUG*/