blob: f7c7647494a6b511c46c0c59ab12488b55b0e959 [file] [log] [blame]
Andy McFadden734155e2009-07-16 18:11:22 -07001/*
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 * Indirect reference table management.
19 */
20#include "Dalvik.h"
21
22/*
23 * Initialize an IndirectRefTable structure.
24 */
25bool dvmInitIndirectRefTable(IndirectRefTable* pRef, int initialCount,
26 int maxCount, IndirectRefKind kind)
27{
28 assert(initialCount > 0);
29 assert(initialCount <= maxCount);
30 assert(kind == kIndirectKindLocal || kind == kIndirectKindGlobal);
31
32 pRef->table = (Object**) malloc(initialCount * sizeof(Object*));
33 if (pRef->table == NULL)
34 return false;
35#ifndef NDEBUG
36 memset(pRef->table, 0xd1, initialCount * sizeof(Object*));
37#endif
Andy McFaddenab00d452009-08-19 07:21:41 -070038 pRef->segmentState.all = IRT_FIRST_SEGMENT;
Andy McFadden734155e2009-07-16 18:11:22 -070039 pRef->allocEntries = initialCount;
40 pRef->maxEntries = maxCount;
41 pRef->kind = kind;
42
43 return true;
44}
45
46/*
47 * Clears out the contents of a IndirectRefTable, freeing allocated storage.
48 */
49void dvmClearIndirectRefTable(IndirectRefTable* pRef)
50{
51 free(pRef->table);
52 pRef->table = NULL;
53 pRef->allocEntries = pRef->maxEntries = -1;
54}
55
56/*
57 * Remove one or more segments from the top. The table entry identified
58 * by "cookie" becomes the new top-most entry.
59 *
60 * Returns false if "cookie" is invalid or the table has only one segment.
61 */
62bool dvmPopIndirectRefTableSegmentCheck(IndirectRefTable* pRef, u4 cookie)
63{
64 IRTSegmentState sst;
65
66 /*
67 * The new value for "top" must be <= the current value. Otherwise
68 * this would represent an expansion of the table.
69 */
70 sst.all = cookie;
71 if (sst.parts.topIndex > pRef->segmentState.parts.topIndex) {
72 LOGE("Attempt to expand table with segment pop (%d to %d)\n",
73 pRef->segmentState.parts.topIndex, sst.parts.topIndex);
74 return false;
75 }
76 if (sst.parts.numHoles >= sst.parts.topIndex) {
77 LOGE("Absurd numHoles in cookie (%d bi=%d)\n",
78 sst.parts.numHoles, sst.parts.topIndex);
79 return false;
80 }
81
82 LOGV("--- after pop, top=%d holes=%d\n",
83 sst.parts.topIndex, sst.parts.numHoles);
84
85 return true;
86}
87
88/*
89 * Make sure that the entry at "idx" is correctly paired with "iref".
90 */
91static bool checkEntry(IndirectRefTable* pRef, IndirectRef iref, int idx)
92{
93 Object* obj = pRef->table[idx];
94 IndirectRef checkRef = dvmObjectToIndirectRef(obj, idx, pRef->kind);
95 if (checkRef != iref) {
96 LOGW("iref mismatch: %p vs %p\n", iref, checkRef);
97 return false;
98 }
99 return true;
100}
101
102/*
103 * Add "obj" to "pRef".
104 */
105IndirectRef dvmAddToIndirectRefTable(IndirectRefTable* pRef, u4 cookie,
106 Object* obj)
107{
108 IRTSegmentState prevState;
109 prevState.all = cookie;
110 int topIndex = pRef->segmentState.parts.topIndex;
111 int bottomIndex = prevState.parts.topIndex;
112
113 assert(obj != NULL);
114 assert(dvmIsValidObject(obj));
115 assert(pRef->table != NULL);
116 assert(pRef->allocEntries <= pRef->maxEntries);
117 assert(pRef->segmentState.parts.numHoles >= prevState.parts.numHoles);
118
119 if (topIndex == pRef->allocEntries) {
120 /* reached end of allocated space; did we hit buffer max? */
121 if (topIndex == pRef->maxEntries) {
Andy McFadden0423f0e2009-08-26 07:21:53 -0700122 LOGW("IndirectRefTable overflow (max=%d)\n", pRef->maxEntries);
Andy McFadden734155e2009-07-16 18:11:22 -0700123 return NULL;
124 }
125
126 Object** newTable;
127 int newSize;
128
129 newSize = pRef->allocEntries * 2;
130 if (newSize > pRef->maxEntries)
131 newSize = pRef->maxEntries;
132 assert(newSize > pRef->allocEntries);
133
134 newTable = (Object**) realloc(pRef->table, newSize * sizeof(Object*));
135 if (newTable == NULL) {
Andy McFadden0423f0e2009-08-26 07:21:53 -0700136 LOGE("Unable to expand iref table (from %d to %d, max=%d)\n",
137 pRef->allocEntries, newSize, pRef->maxEntries);
Andy McFadden734155e2009-07-16 18:11:22 -0700138 return false;
139 }
Andy McFadden0423f0e2009-08-26 07:21:53 -0700140 LOGI("Growing ireftab %p from %d to %d (max=%d)\n",
141 pRef, pRef->allocEntries, newSize, pRef->maxEntries);
Andy McFadden734155e2009-07-16 18:11:22 -0700142
143 /* update entries; adjust "nextEntry" in case memory moved */
144 pRef->table = newTable;
145 pRef->allocEntries = newSize;
146 }
147
148 IndirectRef result;
149
150 /*
151 * We know there's enough room in the table. Now we just need to find
152 * the right spot. If there's a hole, find it and fill it; otherwise,
153 * add to the end of the list.
154 */
155 int numHoles = pRef->segmentState.parts.numHoles - prevState.parts.numHoles;
156 if (numHoles > 0) {
157 assert(topIndex > 1);
158 /* find the first hole; likely to be near the end of the list */
159 Object** pScan = &pRef->table[topIndex - 1];
160 assert(*pScan != NULL);
161 while (*--pScan != NULL) {
162 assert(pScan >= pRef->table + bottomIndex);
163 }
164 result = dvmObjectToIndirectRef(obj, pScan - pRef->table, pRef->kind);
165 *pScan = obj;
166 pRef->segmentState.parts.numHoles--;
167 } else {
168 /* add to the end */
169 result = dvmObjectToIndirectRef(obj, topIndex, pRef->kind);
170 pRef->table[topIndex++] = obj;
171 pRef->segmentState.parts.topIndex = topIndex;
172 }
173
174 assert(result != NULL);
175 return result;
176}
177
178/*
179 * Verify that the indirect table lookup is valid.
180 *
181 * Returns "false" if something looks bad.
182 */
183bool dvmGetFromIndirectRefTableCheck(IndirectRefTable* pRef, IndirectRef iref)
184{
Andy McFaddenab00d452009-08-19 07:21:41 -0700185 if (dvmGetIndirectRefType(iref) == kIndirectKindInvalid) {
186 LOGW("Invalid indirect reference 0x%08x\n", (u4) iref);
187 return false;
188 }
189
Andy McFadden734155e2009-07-16 18:11:22 -0700190 int topIndex = pRef->segmentState.parts.topIndex;
191 int idx = dvmIndirectRefToIndex(iref);
192
193 if (iref == NULL) {
194 LOGI("--- lookup on NULL iref\n");
195 return false;
196 }
197 if (idx >= topIndex) {
198 /* bad -- stale reference? */
199 LOGI("Attempt to access invalid index %d (top=%d)\n",
200 idx, topIndex);
201 return false;
202 }
203
204 Object* obj = pRef->table[idx];
205 if (obj == NULL) {
206 LOGI("Attempt to read from hole, iref=%p\n", iref);
207 return false;
208 }
209 if (!checkEntry(pRef, iref, idx))
210 return false;
211
212 return true;
213}
214
215/*
216 * Remove "obj" from "pRef". We extract the table offset bits from "iref"
217 * and zap the corresponding entry, leaving a hole if it's not at the top.
218 *
219 * If the entry is not between the current top index and the bottom index
220 * specified by the cookie, we don't remove anything. This is the behavior
221 * required by JNI's DeleteLocalRef function.
222 *
223 * Returns "false" if nothing was removed.
224 */
225bool dvmRemoveFromIndirectRefTable(IndirectRefTable* pRef, u4 cookie,
226 IndirectRef iref)
227{
228 IRTSegmentState prevState;
229 prevState.all = cookie;
230 int topIndex = pRef->segmentState.parts.topIndex;
231 int bottomIndex = prevState.parts.topIndex;
232
233 assert(pRef->table != NULL);
234 assert(pRef->allocEntries <= pRef->maxEntries);
235 assert(pRef->segmentState.parts.numHoles >= prevState.parts.numHoles);
236
237 int idx = dvmIndirectRefToIndex(iref);
238 if (idx < bottomIndex) {
239 /* wrong segment */
240 LOGV("Attempt to remove index outside index area (%d vs %d-%d)\n",
241 idx, bottomIndex, topIndex);
242 return false;
243 }
244 if (idx >= topIndex) {
245 /* bad -- stale reference? */
246 LOGI("Attempt to remove invalid index %d (bottom=%d top=%d)\n",
247 idx, bottomIndex, topIndex);
248 return false;
249 }
250
251 if (idx == topIndex-1) {
252 /*
253 * Top-most entry. Scan up and consume holes. No need to NULL
254 * out the entry, since the test vs. topIndex will catch it.
255 */
256 if (!checkEntry(pRef, iref, idx))
257 return false;
258
259#ifndef NDEBUG
260 pRef->table[idx] = (IndirectRef) 0xd3d3d3d3;
261#endif
262
263 int numHoles =
264 pRef->segmentState.parts.numHoles - prevState.parts.numHoles;
265 if (numHoles != 0) {
266 while (--topIndex > bottomIndex && numHoles != 0) {
267 LOGV("+++ checking for hole at %d (cookie=0x%08x) val=%p\n",
268 topIndex-1, cookie, pRef->table[topIndex-1]);
269 if (pRef->table[topIndex-1] != NULL)
270 break;
271 LOGV("+++ ate hole at %d\n", topIndex-1);
272 numHoles--;
273 }
274 pRef->segmentState.parts.numHoles =
275 numHoles + prevState.parts.numHoles;
276 pRef->segmentState.parts.topIndex = topIndex;
277 } else {
278 pRef->segmentState.parts.topIndex = topIndex-1;
279 LOGV("+++ ate last entry %d\n", topIndex-1);
280 }
281 } else {
282 /*
283 * Not the top-most entry. This creates a hole. We NULL out the
284 * entry to prevent somebody from deleting it twice and screwing up
285 * the hole count.
286 */
287 if (pRef->table[idx] == NULL) {
288 LOGV("--- WEIRD: removing null entry %d\n", idx);
289 return false;
290 }
291 if (!checkEntry(pRef, iref, idx))
292 return false;
293
294 pRef->table[idx] = NULL;
295 pRef->segmentState.parts.numHoles++;
296 LOGV("+++ left hole at %d, holes=%d\n",
297 idx, pRef->segmentState.parts.numHoles);
298 }
299
300 return true;
301}
302
303/*
304 * This is a qsort() callback. We sort Object* by class, allocation size,
305 * and then by the Object* itself.
306 */
307static int compareObject(const void* vobj1, const void* vobj2)
308{
309 Object* obj1 = *((Object**) vobj1);
310 Object* obj2 = *((Object**) vobj2);
311
312 /* ensure null references appear at the end */
313 if (obj1 == NULL) {
314 if (obj2 == NULL) {
315 return 0;
316 } else {
317 return 1;
318 }
319 } else if (obj2 == NULL) {
320 return -1;
321 }
322
323 if (obj1->clazz != obj2->clazz) {
324 return (u1*)obj1->clazz - (u1*)obj2->clazz;
325 } else {
326 int size1 = dvmObjectSizeInHeap(obj1);
327 int size2 = dvmObjectSizeInHeap(obj2);
328 if (size1 != size2) {
329 return size1 - size2;
330 } else {
331 return (u1*)obj1 - (u1*)obj2;
332 }
333 }
334}
335
336/*
337 * Log an object with some additional info.
338 *
339 * Pass in the number of additional elements that are identical to or
340 * equivalent to the original.
341 */
342static void logObject(Object* obj, int size, int identical, int equiv)
343{
344 if (obj == NULL) {
345 LOGW(" NULL reference (count=%d)\n", equiv);
346 return;
347 }
348
349 if (identical + equiv != 0) {
350 LOGW("%5d of %s %dB (%d unique)\n", identical + equiv +1,
351 obj->clazz->descriptor, size, equiv +1);
352 } else {
353 LOGW("%5d of %s %dB\n", identical + equiv +1,
354 obj->clazz->descriptor, size);
355 }
356}
357
358/*
359 * Dump the contents of a IndirectRefTable to the log.
360 */
361void dvmDumpIndirectRefTable(const IndirectRefTable* pRef, const char* descr)
362{
363 const int kLast = 10;
364 int count = dvmIndirectRefTableEntries(pRef);
365 Object** refs;
366 int i;
367
368 if (count == 0) {
369 LOGW("Reference table has no entries\n");
370 return;
371 }
372 assert(count > 0);
373
374 /*
375 * Dump the most recent N entries. If there are holes, we will show
376 * fewer than N.
377 */
378 LOGW("Last %d entries in %s reference table:\n", kLast, descr);
379 refs = pRef->table; // use unsorted list
380 int size;
381 int start = count - kLast;
382 if (start < 0)
383 start = 0;
384
385 for (i = start; i < count; i++) {
386 if (refs[i] == NULL)
387 continue;
388 size = dvmObjectSizeInHeap(refs[i]);
389 Object* ref = refs[i];
390 if (ref->clazz == gDvm.classJavaLangClass) {
391 ClassObject* clazz = (ClassObject*) ref;
392 LOGW("%5d: %p cls=%s '%s' (%d bytes)\n", i, ref,
393 (refs[i] == NULL) ? "-" : ref->clazz->descriptor,
394 clazz->descriptor, size);
395 } else {
396 LOGW("%5d: %p cls=%s (%d bytes)\n", i, ref,
397 (refs[i] == NULL) ? "-" : ref->clazz->descriptor, size);
398 }
399 }
400
401 /*
402 * Make a copy of the table, and sort it.
403 *
404 * The NULL "holes" wind up at the end, so we can strip them off easily.
405 */
406 Object** tableCopy = (Object**)malloc(sizeof(Object*) * count);
407 memcpy(tableCopy, pRef->table, sizeof(Object*) * count);
408 qsort(tableCopy, count, sizeof(Object*), compareObject);
409 refs = tableCopy; // use sorted list
410
411 {
412 int q;
413 for (q = 0; q < count; q++)
414 LOGI("%d %p\n", q, refs[q]);
415 }
416
417 int holes = 0;
418 while (refs[count-1] == NULL) {
419 count--;
420 holes++;
421 }
422
423 /*
424 * Dump uniquified table summary. While we're at it, generate a
425 * cumulative total amount of pinned memory based on the unique entries.
426 */
427 LOGW("%s reference table summary (%d entries / %d holes):\n",
428 descr, count, holes);
429 int equiv, identical, total;
430 total = equiv = identical = 0;
431 for (i = 1; i < count; i++) {
432 size = dvmObjectSizeInHeap(refs[i-1]);
433
434 if (refs[i] == refs[i-1]) {
435 /* same reference, added more than once */
436 identical++;
437 } else if (refs[i]->clazz == refs[i-1]->clazz &&
438 (int) dvmObjectSizeInHeap(refs[i]) == size)
439 {
440 /* same class / size, different object */
441 total += size;
442 equiv++;
443 } else {
444 /* different class */
445 total += size;
446 logObject(refs[i-1], size, identical, equiv);
447 equiv = identical = 0;
448 }
449 }
450
451 /* handle the last entry (everything above outputs refs[i-1]) */
452 size = (refs[count-1] == NULL) ? 0 : dvmObjectSizeInHeap(refs[count-1]);
453 total += size;
454 logObject(refs[count-1], size, identical, equiv);
455
456 LOGW("Memory held directly by native code is %d bytes\n", total);
457 free(tableCopy);
458}
459