1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/*
18 * Test the indirect reference table implementation.
19 */
20#include "Dalvik.h"
21
22#include <stdlib.h>
23#include <sys/time.h>
24
25#ifndef NDEBUG
26
27#define DBUG_MSG    ALOGI
28
29class Stopwatch {
30public:
31    Stopwatch() {
32        reset();
33    }
34
35    void reset() {
36        start_ = now();
37    }
38
39    float elapsedSeconds() {
40        return (now() - start_) * 0.000001f;
41    }
42
43private:
44    u8 start_;
45
46    static u8 now() {
47#ifdef HAVE_POSIX_CLOCKS
48        struct timespec tm;
49        clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tm);
50        return tm.tv_sec * 1000000LL + tm.tv_nsec / 1000;
51#else
52        struct timeval tv;
53        gettimeofday(&tv, NULL);
54        return tv.tv_sec * 1000000LL + tv.tv_usec;
55#endif
56    }
57};
58
59/*
60 * Basic add/get/delete tests in an unsegmented table.
61 */
62static bool basicTest()
63{
64    static const int kTableMax = 20;
65    IndirectRefTable irt;
66    IndirectRef iref0, iref1, iref2, iref3;
67    IndirectRef manyRefs[kTableMax];
68    ClassObject* clazz = dvmFindClass("Ljava/lang/Object;", NULL);
69    Object* obj0 = dvmAllocObject(clazz, ALLOC_DONT_TRACK);
70    Object* obj1 = dvmAllocObject(clazz, ALLOC_DONT_TRACK);
71    Object* obj2 = dvmAllocObject(clazz, ALLOC_DONT_TRACK);
72    Object* obj3 = dvmAllocObject(clazz, ALLOC_DONT_TRACK);
73    const u4 cookie = IRT_FIRST_SEGMENT;
74    bool result = false;
75
76    if (!irt.init(kTableMax/2, kTableMax, kIndirectKindGlobal)) {
77        return false;
78    }
79
80    iref0 = (IndirectRef) 0x11110;
81    if (irt.remove(cookie, iref0)) {
82        ALOGE("unexpectedly successful removal");
83        goto bail;
84    }
85
86    /*
87     * Add three, check, remove in the order in which they were added.
88     */
89    DBUG_MSG("+++ START fifo\n");
90    iref0 = irt.add(cookie, obj0);
91    iref1 = irt.add(cookie, obj1);
92    iref2 = irt.add(cookie, obj2);
93    if (iref0 == NULL || iref1 == NULL || iref2 == NULL) {
94        ALOGE("trivial add1 failed");
95        goto bail;
96    }
97
98    if (irt.get(iref0) != obj0 ||
99            irt.get(iref1) != obj1 ||
100            irt.get(iref2) != obj2) {
101        ALOGE("objects don't match expected values %p %p %p vs. %p %p %p",
102                irt.get(iref0), irt.get(iref1), irt.get(iref2),
103                obj0, obj1, obj2);
104        goto bail;
105    } else {
106        DBUG_MSG("+++ obj1=%p --> iref1=%p\n", obj1, iref1);
107    }
108
109    if (!irt.remove(cookie, iref0) ||
110            !irt.remove(cookie, iref1) ||
111            !irt.remove(cookie, iref2))
112    {
113        ALOGE("fifo deletion failed");
114        goto bail;
115    }
116
117    /* table should be empty now */
118    if (irt.capacity() != 0) {
119        ALOGE("fifo del not empty");
120        goto bail;
121    }
122
123    /* get invalid entry (off the end of the list) */
124    if (irt.get(iref0) != kInvalidIndirectRefObject) {
125        ALOGE("stale entry get succeeded unexpectedly");
126        goto bail;
127    }
128
129    /*
130     * Add three, remove in the opposite order.
131     */
132    DBUG_MSG("+++ START lifo\n");
133    iref0 = irt.add(cookie, obj0);
134    iref1 = irt.add(cookie, obj1);
135    iref2 = irt.add(cookie, obj2);
136    if (iref0 == NULL || iref1 == NULL || iref2 == NULL) {
137        ALOGE("trivial add2 failed");
138        goto bail;
139    }
140
141    if (!irt.remove(cookie, iref2) ||
142            !irt.remove(cookie, iref1) ||
143            !irt.remove(cookie, iref0))
144    {
145        ALOGE("lifo deletion failed");
146        goto bail;
147    }
148
149    /* table should be empty now */
150    if (irt.capacity() != 0) {
151        ALOGE("lifo del not empty");
152        goto bail;
153    }
154
155    /*
156     * Add three, remove middle / middle / bottom / top.  (Second attempt
157     * to remove middle should fail.)
158     */
159    DBUG_MSG("+++ START unorder\n");
160    iref0 = irt.add(cookie, obj0);
161    iref1 = irt.add(cookie, obj1);
162    iref2 = irt.add(cookie, obj2);
163    if (iref0 == NULL || iref1 == NULL || iref2 == NULL) {
164        ALOGE("trivial add3 failed");
165        goto bail;
166    }
167
168    if (irt.capacity() != 3) {
169        ALOGE("expected 3 entries, found %d", irt.capacity());
170        goto bail;
171    }
172
173    if (!irt.remove(cookie, iref1) || irt.remove(cookie, iref1)) {
174        ALOGE("unorder deletion1 failed");
175        goto bail;
176    }
177
178    /* get invalid entry (from hole) */
179    if (irt.get(iref1) != kInvalidIndirectRefObject) {
180        ALOGE("hole get succeeded unexpectedly");
181        goto bail;
182    }
183
184    if (!irt.remove(cookie, iref2) || !irt.remove(cookie, iref0)) {
185        ALOGE("unorder deletion2 failed");
186        goto bail;
187    }
188
189    /* table should be empty now */
190    if (irt.capacity() != 0) {
191        ALOGE("unorder del not empty");
192        goto bail;
193    }
194
195    /*
196     * Add four entries.  Remove #1, add new entry, verify that table size
197     * is still 4 (i.e. holes are getting filled).  Remove #1 and #3, verify
198     * that we delete one and don't hole-compact the other.
199     */
200    DBUG_MSG("+++ START hole fill\n");
201    iref0 = irt.add(cookie, obj0);
202    iref1 = irt.add(cookie, obj1);
203    iref2 = irt.add(cookie, obj2);
204    iref3 = irt.add(cookie, obj3);
205    if (iref0 == NULL || iref1 == NULL || iref2 == NULL || iref3 == NULL) {
206        ALOGE("trivial add4 failed");
207        goto bail;
208    }
209    if (!irt.remove(cookie, iref1)) {
210        ALOGE("remove 1 of 4 failed");
211        goto bail;
212    }
213    iref1 = irt.add(cookie, obj1);
214    if (irt.capacity() != 4) {
215        ALOGE("hole not filled");
216        goto bail;
217    }
218    if (!irt.remove(cookie, iref1) || !irt.remove(cookie, iref3)) {
219        ALOGE("remove 1/3 failed");
220        goto bail;
221    }
222    if (irt.capacity() != 3) {
223        ALOGE("should be 3 after two deletions");
224        goto bail;
225    }
226    if (!irt.remove(cookie, iref2) || !irt.remove(cookie, iref0)) {
227        ALOGE("remove 2/0 failed");
228        goto bail;
229    }
230    if (irt.capacity() != 0) {
231        ALOGE("not empty after split remove");
232        goto bail;
233    }
234
235    /*
236     * Add an entry, remove it, add a new entry, and try to use the original
237     * iref.  They have the same slot number but are for different objects.
238     * With the extended checks in place, this should fail.
239     */
240    DBUG_MSG("+++ START switched\n");
241    iref0 = irt.add(cookie, obj0);
242    irt.remove(cookie, iref0);
243    iref1 = irt.add(cookie, obj1);
244    if (irt.remove(cookie, iref0)) {
245        ALOGE("mismatched del succeeded (%p vs %p)", iref0, iref1);
246        goto bail;
247    }
248    if (!irt.remove(cookie, iref1)) {
249        ALOGE("switched del failed");
250        goto bail;
251    }
252    if (irt.capacity() != 0) {
253        ALOGE("switching del not empty");
254        goto bail;
255    }
256
257    /*
258     * Same as above, but with the same object.  A more rigorous checker
259     * (e.g. with slot serialization) will catch this.
260     */
261    DBUG_MSG("+++ START switched same object\n");
262    iref0 = irt.add(cookie, obj0);
263    irt.remove(cookie, iref0);
264    iref1 = irt.add(cookie, obj0);
265    if (iref0 != iref1) {
266        /* try 0, should not work */
267        if (irt.remove(cookie, iref0)) {
268            ALOGE("temporal del succeeded (%p vs %p)", iref0, iref1);
269            goto bail;
270        }
271    }
272    if (!irt.remove(cookie, iref1)) {
273        ALOGE("temporal cleanup failed");
274        goto bail;
275    }
276    if (irt.capacity() != 0) {
277        ALOGE("temporal del not empty");
278        goto bail;
279    }
280
281    DBUG_MSG("+++ START null lookup\n");
282    if (irt.get(NULL) != kInvalidIndirectRefObject) {
283        ALOGE("null lookup succeeded");
284        goto bail;
285    }
286
287    DBUG_MSG("+++ START stale lookup\n");
288    iref0 = irt.add(cookie, obj0);
289    irt.remove(cookie, iref0);
290    if (irt.get(iref0) != kInvalidIndirectRefObject) {
291        ALOGE("stale lookup succeeded");
292        goto bail;
293    }
294
295    /*
296     * Test table overflow.
297     */
298    DBUG_MSG("+++ START overflow\n");
299    int i;
300    for (i = 0; i < kTableMax; i++) {
301        manyRefs[i] = irt.add(cookie, obj0);
302        if (manyRefs[i] == NULL) {
303            ALOGE("Failed adding %d of %d", i, kTableMax);
304            goto bail;
305        }
306    }
307    if (irt.add(cookie, obj0) != NULL) {
308        ALOGE("Table overflow succeeded");
309        goto bail;
310    }
311    if (irt.capacity() != (size_t)kTableMax) {
312        ALOGE("Expected %d entries, found %d", kTableMax, irt.capacity());
313        goto bail;
314    }
315    irt.dump("table with 20 entries, all filled");
316    for (i = 0; i < kTableMax-1; i++) {
317        if (!irt.remove(cookie, manyRefs[i])) {
318            ALOGE("multi-remove failed at %d", i);
319            goto bail;
320        }
321    }
322    irt.dump("table with 20 entries, 19 of them holes");
323    /* because of removal order, should have 20 entries, 19 of them holes */
324    if (irt.capacity() != (size_t)kTableMax) {
325        ALOGE("Expected %d entries (with holes), found %d",
326                kTableMax, irt.capacity());
327        goto bail;
328    }
329    if (!irt.remove(cookie, manyRefs[kTableMax-1])) {
330        ALOGE("multi-remove final failed");
331        goto bail;
332    }
333    if (irt.capacity() != 0) {
334        ALOGE("multi-del not empty");
335        goto bail;
336    }
337
338    /* Done */
339    DBUG_MSG("+++ basic test complete\n");
340    result = true;
341
342bail:
343    irt.destroy();
344    return result;
345}
346
347static bool performanceTest()
348{
349    static const int kTableMax = 100;
350    IndirectRefTable irt;
351    IndirectRef manyRefs[kTableMax];
352    ClassObject* clazz = dvmFindClass("Ljava/lang/Object;", NULL);
353    Object* obj0 = dvmAllocObject(clazz, ALLOC_DONT_TRACK);
354    const u4 cookie = IRT_FIRST_SEGMENT;
355    const int kLoops = 100000;
356    Stopwatch stopwatch;
357
358    DBUG_MSG("+++ START performance\n");
359
360    if (!irt.init(kTableMax, kTableMax, kIndirectKindGlobal)) {
361        return false;
362    }
363
364    stopwatch.reset();
365    for (int loop = 0; loop < kLoops; loop++) {
366        for (int i = 0; i < kTableMax; i++) {
367            manyRefs[i] = irt.add(cookie, obj0);
368        }
369        for (int i = 0; i < kTableMax; i++) {
370            irt.remove(cookie, manyRefs[i]);
371        }
372    }
373    DBUG_MSG("Add/remove %d objects FIFO order, %d iterations, %0.3fms / iteration",
374            kTableMax, kLoops, stopwatch.elapsedSeconds() * 1000 / kLoops);
375
376    stopwatch.reset();
377    for (int loop = 0; loop < kLoops; loop++) {
378        for (int i = 0; i < kTableMax; i++) {
379            manyRefs[i] = irt.add(cookie, obj0);
380        }
381        for (int i = kTableMax; i-- > 0; ) {
382            irt.remove(cookie, manyRefs[i]);
383        }
384    }
385    DBUG_MSG("Add/remove %d objects LIFO order, %d iterations, %0.3fms / iteration",
386            kTableMax, kLoops, stopwatch.elapsedSeconds() * 1000  / kLoops);
387
388    for (int i = 0; i < kTableMax; i++) {
389        manyRefs[i] = irt.add(cookie, obj0);
390    }
391    stopwatch.reset();
392    for (int loop = 0; loop < kLoops; loop++) {
393        for (int i = 0; i < kTableMax; i++) {
394            irt.get(manyRefs[i]);
395        }
396    }
397    DBUG_MSG("Get %d objects, %d iterations, %0.3fms / iteration",
398            kTableMax, kLoops, stopwatch.elapsedSeconds() * 1000  / kLoops);
399    for (int i = kTableMax; i-- > 0; ) {
400        irt.remove(cookie, manyRefs[i]);
401    }
402
403    irt.destroy();
404    return true;
405}
406
407/*
408 * Some quick tests.
409 */
410bool dvmTestIndirectRefTable()
411{
412    if (!basicTest()) {
413        ALOGE("IRT basic test failed");
414        return false;
415    }
416
417    if (!performanceTest()) {
418        ALOGE("IRT performance test failed");
419        return false;
420    }
421
422    return true;
423}
424
425#endif /*NDEBUG*/
426