blob: 69657e3e4c9a88d786a69e9fef6b5e54fad9770a [file] [log] [blame]
The Android Open Source Projectf6c38712009-03-03 19:28:47 -08001/*
2 * Copyright (C) 2008 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 * Compress the range of "constant pool" indexes in instructions and
19 * annotations to lower runtime RAM footprint.
20 *
21 * NOTE: this is an incomplete experimental feature. Do not try to use it.
22 */
23#include "Dalvik.h"
24#include "libdex/InstrUtils.h"
25#include "libdex/OptInvocation.h"
26#include "libdex/DexClass.h"
27
28/*
29Overview
30
31When a class, method, field, or string constant is referred to from
32Dalvik bytecode, the reference takes the form of an integer index value.
33This value indexes into an array of type_id_item, method_id_item,
34field_id_item, or string_id_item in the DEX file. The first three
35themselves contain (directly or indirectly) indexes to strings that the
36resolver uses to convert the instruction stream index into a pointer to
37the appropriate object or struct.
38
39For example, an invoke-virtual instruction needs to specify which method
40is to be invoked. The method constant indexes into the method_id_item
41array, each entry of which has indexes that specify the defining class
42(type_id_item), method name (string_id_item), and method prototype
43(proto_id_item). The type_id_item just holds an index to a string_id_item,
44which holds the file offset to the string with the class name. The VM
45finds the class by name, then searches through the class' table of virtual
46methods to find one with a matching name and prototype.
47
48This process is fairly expensive, so after the first time it completes
49successfully, the VM records that the method index resolved to a specific
50Method struct. On subsequent execution, the VM just pulls the Method ptr
51out of the resolved-methods array. A similar approach is used with
52the indexes for classes, fields, and string constants.
53
54The problem with this approach is that we need to have a "resolved" entry
55for every possible class, method, field, and string constant in every
56DEX file, even if some of those aren't used from code. The DEX string
57constant table has entries for method prototypes and class names that are
58never used by the code, and "public static final" fields often turn into
59immediate constants. The resolution table entries are only 4 bytes each,
60but there are roughly 200,000 of them in the bootstrap classes alone.
61
62DEX optimization removes many index references by replacing virtual method
63indexes with vtable offsets and instance field indexes with byte offsets.
64In the earlier example, the method would be resolved at "dexopt" time, and
65the instruction rewritten as invoke-virtual-quick with the vtable offset.
66
67(There are comparatively few classes compared to other constant pool
68entries, and a much higher percentage (typically 60-70%) are used. The
69biggest gains come from the string pool.)
70
71Using the resolved-entity tables provides a substantial performance
72improvement, but results in applications allocating 1MB+ of tables that
73are 70% unused. The used and unused entries are freely intermixed,
74preventing effective sharing with the zygote process, and resulting in
75large numbers of private/dirty pages on the native heap as the tables
76populate on first use.
77
78The trick is to reduce the memory usage without decreasing performance.
79Using smaller resolved-entity tables can actually give us a speed boost,
80because we'll have a smaller "live" set of pages and make more effective
81use of the data cache.
82
83
84The approach we're going to use is to determine the set of indexes that
85could potentially be resolved, generate a mapping from the minimal set to
86the full set, and append the mapping to the DEX file. This is done at
87"dexopt" time, because we need to keep the changes in shared/read-only
88pages or we'll lose the benefits of doing the work.
89
90There are two ways to create and use the new mapping:
91
92 (1) Write the entire full->minimal mapping to the ".odex" file. On every
93 instruction that uses an index, use the mapping to determine the
94 "compressed" constant value, and then use that to index into the
95 resolved-entity tables on the heap. The instruction stream is unchanged,
96 and the resolver can easily tell if a given index is cacheable.
97
98 (2) Write the inverse miminal->full mapping to the ".odex" file, and
99 rewrite the constants in the instruction stream. The interpreter is
100 unchanged, and the resolver code uses the mapping to find the original
101 data in the DEX.
102
103Approach #1 is easier and safer to implement, but it requires a table
104lookup every time we execute an instruction that includes a constant
105pool reference. This causes an unacceptable performance hit, chiefly
106because we're hitting semi-random memory pages and hosing the data cache.
107This is mitigated somewhat by DEX optimizations that replace the constant
108with a vtable index or field byte offset. Approach #1 also requires
109a larger map table, increasing the size of the DEX on disk. One nice
110property of approach #1 is that most of the DEX file is unmodified,
111so use of the mapping is a runtime decision.
112
113Approach #2 is preferred for performance reasons.
114
115
116The class/method/field/string resolver code has to handle indices from
117three sources: interpreted instructions, annotations, and exception
118"catch" lists. Sometimes these occur indirectly, e.g. we need to resolve
119the declaring class associated with fields and methods when the latter
120two are themselves resolved. Parsing and rewriting instructions is fairly
121straightforward, but annotations use a complex format with variable-width
122index values.
123
124We can safely rewrite index values in annotations if we guarantee that the
125new value is smaller than the original. This implies a two-pass approach:
126the first determines the set of indexes actually used, the second does the
127rewrite. Doing the rewrite in a single pass would be much harder.
128
129Instances of the "original" indices will still be found in the file; if
130we try to be all-inclusive we will include some stuff that doesn't need
131to be there (e.g. we don't generally need to cache the class name string
132index result, since once we have the class resolved we don't need to look
133it up by name through the resolver again). There is some potential for
134performance improvement by caching more than we strictly need, but we can
135afford to give up a little performance during class loading if it allows
136us to regain some memory.
137
138For safety and debugging, it's useful to distinguish the "compressed"
139constants in some way, e.g. setting the high bit when we rewrite them.
140In practice we don't have any free bits: indexes are usually 16-bit
141values, and we have more than 32,767 string constants in at least one of
142our core DEX files. Also, this does not work with constants embedded in
143annotations, because of the variable-width encoding.
144
145We should be safe if we can establish a clear distinction between sources
146of "original" and "compressed" indices. If the values get crossed up we
147can end up with elusive bugs. The easiest approach is to declare that
148only indices pulled from certain locations (the instruction stream and/or
149annotations) are compressed. This prevents us from adding indices in
150arbitrary locations to the compressed set, but should allow a reasonably
151robust implementation.
152
153
154Further implementation thoughts:
155
156 - We don't have to do annotations in the first pass. At heart the
157 resolved entity cache is a performance optimization, not necessary for
158 correctness, and we're not making annotation performance a priority
159 at this stage.
160 - The most important "fast path" is instruction processing. Everything
161 else can do additional work without having a measurable impact.
162 However...
163 - We need to keep an eye on uncached resolves to ensure that we haven't
164 introduced noticeable performance losses. In particular, the use of
165 runtime annotations with string constants may suffer if we don't include
166 annotation rewriting in the solution.
167 - We can have separate resolver functions for "original" and "compressed"
168 indices. This way we don't have to add a flag argument to the resolver
169 functions (which would require passing an additional parameter in from
170 the interpreter).
171 - The VM spec has some specific things to say about string constant
172 equality and interning. Index compression should have no effect on
173 that; we just change how long it takes to find the interned string in
174 certain circumstances. The impact can be mitigated somewhat by
175 improving the performance of the interned string table code.
176 - This can make e.g. method resolution slower. The method_id_item has
177 an index to a method name string, and we will no longer cache the
178 result of resolving that string. This impacts resolution of any method
179 with the same name as a previously-resolved method.
180 - We may need to tweak the tools, particularly "dexdump", to show the
181 translated values.
182 - We can use 16-bit values in the mapping table, since we should have
183 fewer than 2^16 remapped entries. If we overflow we can skip the remap
184 for that table or for the entire DEX file. The resolver will need to
185 check for the existence of the table to determine whether or not entries
186 must be remapped. The cost of the extra check is acceptable for
187 approach #2, since it's only at resolve time, but may be undesirable
188 for approach #1.
189*/
190/*
191Output Formats
Carl Shapirode750892010-06-08 16:37:12 -0700192
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800193There are two possible output formats, from which we choose based on how
194we plan to take advantage of the remapped constants. At most one of these
195will appear in the DEX.
196
197NOTE: if EIXM appears in the DEX, the VM *must* be configured with
198DVM_RESOLVER_CACHE=DVM_RC_EXPANDING (2). Otherwise the constants we
199pull from the instruction stream will be wrong and we will fail quickly.
200
201For approach #1: map from original indices to the reduced set.
202
203 This includes the four "mapToNew" tables.
204
205 Format (RIXM):
206 u4 classCount // #of entries in classMap[]; == typeIdsSize
207 u4 reducedClassCount // #of entries in remapped table (for alloc)
208 u2 classMap[]
209 u4 methodCount
210 u4 reducedMethodCount
211 u2 methodMap[]
212 u4 fieldCount
213 u4 reducedFieldCount
214 u2 fieldMap[]
215 u4 stringCount
216 u4 reducedStringCount
217 u2 stringMap[]
218
219For approach #2: map from the reduced set back to the originals.
220
221 This includes the four "mapToOld" tables.
222
223 Format (EIXM):
224 u4 classCount // #of entries in classMap[]; post-reduction
225 u2 classMap[]
226 u4 methodCount
227 u2 methodMap[]
228 u4 fieldCount
229 u2 fieldMap[]
230 u4 stringCount
231 u2 stringMap[]
232
233The arrays are padded so that the "count" values are always aligned on
23432-bit boundaries. All multi-byte values are in native host order.
235*/
236
237
238/*
239 * Gather results from the post-optimization instruction scan.
240 */
241typedef struct ScanResults {
242 /* output */
243 BitVector* usedClasses;
244 BitVector* usedMethods;
245 BitVector* usedFields;
246 BitVector* usedStrings;
247} ScanResults;
248
249/* prototype for the for-all-methods function */
250typedef void (AllMethodsFunc)(DexFile* pDexFile, const char* classDescriptor,
251 DexMethod* pDexMethod, void* arg);
252
253
254/*
255 * Free scan results.
256 */
257static void freeScanResults(ScanResults* pResults)
258{
259 if (pResults == NULL)
260 return;
261
262 dvmFreeBitVector(pResults->usedClasses);
263 dvmFreeBitVector(pResults->usedMethods);
264 dvmFreeBitVector(pResults->usedFields);
265 dvmFreeBitVector(pResults->usedStrings);
266 free(pResults);
267}
268
269/*
270 * Allocate storage for the results of the instruction scan.
271 */
272static ScanResults* allocScanResults(const DexFile* pDexFile)
273{
274 ScanResults* pResults;
275 const DexHeader* pHeader = pDexFile->pHeader;
276
277 pResults = (ScanResults*) calloc(1, sizeof(ScanResults));
278 if (pResults == NULL)
279 return NULL;
280
281 pResults->usedClasses = dvmAllocBitVector(pHeader->typeIdsSize, false);
282 pResults->usedMethods = dvmAllocBitVector(pHeader->methodIdsSize, false);
283 pResults->usedFields = dvmAllocBitVector(pHeader->fieldIdsSize, false);
284 pResults->usedStrings = dvmAllocBitVector(pHeader->stringIdsSize, false);
285
286 if (pResults->usedClasses == NULL ||
287 pResults->usedMethods == NULL ||
288 pResults->usedFields == NULL ||
289 pResults->usedStrings == NULL)
290 {
291 freeScanResults(pResults);
292 return NULL;
293 }
294
295 return pResults;
296}
297
298/*
299 * Call "func(method, arg)" on all methods in the specified class.
300 *
301 * Pass in a pointer to the class_data_item, positioned at the start of
302 * the field data (i.e. just past the class data header).
303 *
304 * "classDescriptor" is for debug messages.
305 */
306static void forAllMethodsInClass(DexFile* pDexFile, const u1** ppEncodedData,
307 const DexClassDataHeader* pHeader, const char* classDescriptor,
308 AllMethodsFunc func, void* arg)
309{
310 int i;
311
312 /*
313 * Consume field data.
314 */
315 if (pHeader->staticFieldsSize != 0) {
316 int count = (int) pHeader->staticFieldsSize;
317 u4 lastIndex = 0;
318 DexField field;
319 for (i = 0; i < count; i++) {
320 dexReadClassDataField(ppEncodedData, &field, &lastIndex);
321 }
322 }
323 if (pHeader->instanceFieldsSize != 0) {
324 int count = (int) pHeader->instanceFieldsSize;
325 u4 lastIndex = 0;
326 DexField field;
327 for (i = 0; i < count; i++) {
328 dexReadClassDataField(ppEncodedData, &field, &lastIndex);
329 }
330 }
331
332 /*
333 * Run through all methods.
334 */
335 if (pHeader->directMethodsSize != 0) {
336 int count = (int) pHeader->directMethodsSize;
337 u4 lastIndex = 0;
338 DexMethod method;
339
340 for (i = 0; i < count; i++) {
341 dexReadClassDataMethod(ppEncodedData, &method, &lastIndex);
342 (func)(pDexFile, classDescriptor, &method, arg);
343 }
344 }
345 if (pHeader->virtualMethodsSize != 0) {
346 int count = (int) pHeader->virtualMethodsSize;
347 u4 lastIndex = 0;
348 DexMethod method;
349
350 for (i = 0; i < count; i++) {
351 dexReadClassDataMethod(ppEncodedData, &method, &lastIndex);
352 (func)(pDexFile, classDescriptor, &method, arg);
353 }
354 }
355}
356
357/*
358 * Call "func(method, arg)" on all methods in all classes in DexFile.
359 */
360static void forAllMethods(DexFile* pDexFile, AllMethodsFunc func, void* arg)
361{
362 u4 count = pDexFile->pHeader->classDefsSize;
363 u4 idx;
364
365 for (idx = 0; idx < count; idx++) {
366 const DexClassDef* pClassDef;
367 DexClassDataHeader header;
368 const u1* pEncodedData;
369
370 pClassDef = dexGetClassDef(pDexFile, idx);
371 pEncodedData = dexGetClassData(pDexFile, pClassDef);
372
373 const char* classDescriptor;
374 classDescriptor = dexStringByTypeIdx(pDexFile, pClassDef->classIdx);
375
376 if (pEncodedData != NULL) {
377 dexReadClassDataHeader(&pEncodedData, &header);
378
379 forAllMethodsInClass(pDexFile, &pEncodedData, &header,
380 classDescriptor, func, arg);
381 } else {
382 //printf("%s: no class data\n", classDescriptor);
383 /* no class data, e.g. "marker interface" */
384 }
385 }
386}
387
388/*
389 * Mark a class ID as referenced.
390 */
391static void markClass(const u2* ptr, ScanResults* pResults)
392{
393 u2 classIdx = *ptr;
394 if (!dvmSetBit(pResults->usedClasses, classIdx)) {
395 LOGE("Unable to mark class %d as in-use\n", classIdx);
396 }
397}
398
399/*
400 * Mark a method ID as referenced.
401 */
402static void markMethod(const u2* ptr, ScanResults* pResults)
403{
404 u2 methodIdx = *ptr;
405 if (!dvmSetBit(pResults->usedMethods, methodIdx)) {
406 LOGE("Unable to mark method %d as in-use\n", methodIdx);
407 }
408}
409
410/*
411 * Mark a field ID as referenced.
412 */
413static void markField(const u2* ptr, ScanResults* pResults)
414{
415 u2 fieldIdx = *ptr;
416 if (!dvmSetBit(pResults->usedFields, fieldIdx)) {
417 LOGE("Unable to mark field %d as in-use\n", fieldIdx);
418 }
419}
420
421/*
422 * Mark a string constant as referenced.
423 */
424static void markString(const u2* ptr, ScanResults* pResults)
425{
426 u2 stringIdx = *ptr;
427 if (!dvmSetBit(pResults->usedStrings, stringIdx)) {
428 LOGE("Unable to mark string %d as in-use\n", stringIdx);
429 }
430}
431
432/*
433 * Mark a "jumbo" string constant as referenced.
434 */
435static void markJumboString(u2* ptr, ScanResults* pResults)
436{
437 u4 stringIdx;
438
439 /* it's in native byte order, but might not be 32-bit aligned */
440 memcpy(&stringIdx, ptr, sizeof(u4));
441 if (!dvmSetBit(pResults->usedStrings, stringIdx)) {
442 LOGE("Unable to mark string %d as in-use\n", stringIdx);
443 }
444}
445
446/*
447 * Remap a value in the instruction stream.
448 */
449static inline void updateValue(u2* ptr, const IndexMapSet* pIndexMapSet,
450 int whichMap)
451{
452 const IndexMap* pMap = &pIndexMapSet->map[whichMap];
453 if (pMap != NULL) {
454 u2 newIdx = pMap->mapToNew[*ptr];
455 assert(newIdx != kNoIndexMapping);
456 *ptr = newIdx;
457 }
458}
459static void updateClass(u2* ptr, const IndexMapSet* pIndexMapSet)
460{
461 updateValue(ptr, pIndexMapSet, kMapClasses);
462}
463static void updateMethod(u2* ptr, const IndexMapSet* pIndexMapSet)
464{
465 updateValue(ptr, pIndexMapSet, kMapMethods);
466}
467static void updateField(u2* ptr, const IndexMapSet* pIndexMapSet)
468{
469 updateValue(ptr, pIndexMapSet, kMapFields);
470}
471static void updateString(u2* ptr, const IndexMapSet* pIndexMapSet)
472{
473 updateValue(ptr, pIndexMapSet, kMapStrings);
474}
475static void updateJumboString(u2* ptr, const IndexMapSet* pIndexMapSet)
476{
477 u4 stringIdx;
478 u4 newIdx;
479
480 /* it's in native byte order, but might not be 32-bit aligned */
481 memcpy(&stringIdx, ptr, sizeof(stringIdx));
482
483 /* get new value */
484 newIdx = pIndexMapSet->map[kMapStrings].mapToNew[*ptr];
485 assert(newIdx != kNoIndexMapping);
486
487 /* copy it out */
488 memcpy(ptr, &newIdx, sizeof(newIdx));
489}
490
491/*
492 * Run through an instructions stream, marking constants as we see them.
493 *
494 * If "pResults" is non-NULL, we populate "pResults" with what we find,
495 * making no changes to the instruction stream.
496 *
497 * If "pIndexMapSet" is non-NULL, we rewrite the constants in the
498 * instruction stream.
499 */
500static void markUsedConstantsFromInsns(u2* insns, u4 insnsSize,
501 ScanResults* pResults, const IndexMapSet* pIndexMapSet)
502{
503 //printf(" %p %u units\n", insns, insnsSize);
504
505 while (insnsSize > 0) {
506 int width;
507 u2* pConst = insns + 1;
508
509 switch (*insns & 0xff) {
510 case OP_IGET:
511 case OP_IGET_WIDE:
512 case OP_IGET_OBJECT:
513 case OP_IGET_BOOLEAN:
514 case OP_IGET_BYTE:
515 case OP_IGET_CHAR:
516 case OP_IGET_SHORT:
517 case OP_IPUT:
518 case OP_IPUT_WIDE:
519 case OP_IPUT_OBJECT:
520 case OP_IPUT_BOOLEAN:
521 case OP_IPUT_BYTE:
522 case OP_IPUT_CHAR:
523 case OP_IPUT_SHORT:
524 case OP_SGET:
525 case OP_SGET_WIDE:
526 case OP_SGET_OBJECT:
527 case OP_SGET_BOOLEAN:
528 case OP_SGET_BYTE:
529 case OP_SGET_CHAR:
530 case OP_SGET_SHORT:
531 case OP_SPUT:
532 case OP_SPUT_WIDE:
533 case OP_SPUT_OBJECT:
534 case OP_SPUT_BOOLEAN:
535 case OP_SPUT_BYTE:
536 case OP_SPUT_CHAR:
537 case OP_SPUT_SHORT:
538 /* instanceop vA, vB, field@CCCC */
539 /* staticop vAA, field@BBBB */
540 if (pResults != NULL)
541 markField(pConst, pResults);
542 else
543 updateField(pConst, pIndexMapSet);
544 break;
545
546 case OP_CONST_STRING:
547 /* const-string vAA, string@BBBB */
548 if (pResults != NULL)
549 markString(pConst, pResults);
550 else
551 updateString(pConst, pIndexMapSet);
552 break;
553
554 case OP_CONST_STRING_JUMBO:
555 /* const-string/jumbo vAA, string@BBBBBBBB */
556 if (pResults != NULL)
557 markJumboString(pConst, pResults);
558 else
559 updateJumboString(pConst, pIndexMapSet);
560 break;
561
562 case OP_CONST_CLASS:
563 case OP_CHECK_CAST:
564 case OP_NEW_INSTANCE:
565 case OP_FILLED_NEW_ARRAY_RANGE:
566 case OP_INSTANCE_OF:
567 case OP_NEW_ARRAY:
568 case OP_FILLED_NEW_ARRAY:
569 /* const-class vAA, type@BBBB */
570 /* check-cast vAA, type@BBBB */
571 /* new-instance vAA, type@BBBB */
572 /* filled-new-array/range {vCCCC .. vNNNN}, type@BBBB */
573 /* instance-of vA, vB, type@CCCC */
574 /* new-array vA, vB, type@CCCC */
575 /* filled-new-array {vD, vE, vF, vG, vA}, type@CCCC */
576 if (pResults != NULL)
577 markClass(pConst, pResults);
578 else
579 updateClass(pConst, pIndexMapSet);
580 break;
581
582 case OP_INVOKE_VIRTUAL:
583 case OP_INVOKE_SUPER:
584 case OP_INVOKE_DIRECT:
585 case OP_INVOKE_STATIC:
586 case OP_INVOKE_INTERFACE:
587 case OP_INVOKE_VIRTUAL_RANGE:
588 case OP_INVOKE_SUPER_RANGE:
589 case OP_INVOKE_DIRECT_RANGE:
590 case OP_INVOKE_STATIC_RANGE:
591 case OP_INVOKE_INTERFACE_RANGE:
592 /* invoke-kind {vD, vE, vF, vG, vA}, meth@CCCC */
593 /* invoke-kind/range {vCCCC .. vNNNN}, meth@BBBB */
594 if (pResults != NULL)
595 markMethod(pConst, pResults);
596 else
597 updateMethod(pConst, pIndexMapSet);
598 break;
599
600 default:
601 // ignore this instruction
602 ;
603 }
604
605 width = dexGetInstrOrTableWidthAbs(gDvm.instrWidth, insns);
606 assert(width > 0 && width <= (int)insnsSize);
607
608 insns += width;
609 insnsSize -= width;
610 }
611}
612
613/*
614 * This is an AllMethodsFunc implementation.
615 *
616 * Run through the instructions in this method, setting bits in the "pResults"
617 * struct as we locate constants.
618 */
619static void markUsedConstants(DexFile* pDexFile, const char* classDescriptor,
620 DexMethod* pDexMethod, void* arg)
621{
622 ScanResults* pResults = (ScanResults*) arg;
623 const DexCode* pDexCode = dexGetCode(pDexFile, pDexMethod);
624
625 if (false) {
626 const DexMethodId* pMethodId;
627 const char* methodName;
628 pMethodId = dexGetMethodId(pDexFile, pDexMethod->methodIdx);
629 methodName = dexStringById(pDexFile, pMethodId->nameIdx);
630 printf(" %s.%s\n", classDescriptor, methodName);
631 }
632
633 if (pDexCode != NULL) {
634 u2* insns = (u2*) pDexCode->insns;
635 u4 insnsSize = pDexCode->insnsSize;
636 markUsedConstantsFromInsns(insns, insnsSize, pResults, NULL);
637 } else {
638 //printf(" (no code)\n");
639 }
640}
641
642/*
643 * This is an AllMethodsFunc implementation.
644 *
645 * Run through the instructions in this method, altering the constants used.
646 */
Carl Shapiroe3c01da2010-05-20 22:54:18 -0700647#if DVM_RESOLVER_CACHE == DVM_RC_EXPANDING
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800648static void updateUsedConstants(DexFile* pDexFile, const char* classDescriptor,
649 DexMethod* pDexMethod, void* arg)
650{
651 const IndexMapSet* pIndexMapSet = (const IndexMapSet*) arg;
652 const DexCode* pDexCode = dexGetCode(pDexFile, pDexMethod);
653
654 if (false) {
655 const DexMethodId* pMethodId;
656 const char* methodName;
657 pMethodId = dexGetMethodId(pDexFile, pDexMethod->methodIdx);
658 methodName = dexStringById(pDexFile, pMethodId->nameIdx);
659 printf(" %s.%s\n", classDescriptor, methodName);
660 }
661
662 if (pDexCode != NULL) {
663 u2* insns = (u2*) pDexCode->insns;
664 u4 insnsSize = pDexCode->insnsSize;
665 markUsedConstantsFromInsns(insns, insnsSize, NULL, pIndexMapSet);
666 } else {
667 //printf(" (no code)\n");
668 }
669}
Carl Shapiroe3c01da2010-05-20 22:54:18 -0700670#endif
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800671
672/*
673 * Count up the bits and show a count.
674 */
675static void showBitCount(const char* label, int setCount, int maxCount)
676{
677 printf("%s: %d of %d (%.1f%% unused)\n", label, setCount, maxCount,
678 ((maxCount - setCount) * 100.0f) / maxCount);
679}
680
681/*
682 * Print some debug info.
683 */
684static void summarizeResults(DvmDex* pDvmDex, ScanResults* pResults)
685{
686 DexFile* pDexFile = pDvmDex->pDexFile;
Carl Shapiroe3c01da2010-05-20 22:54:18 -0700687#if 0
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800688 int i;
689
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800690 for (i = 0; i < (int) pDvmDex->pDexFile->pHeader->typeIdsSize; i++) {
691 const DexTypeId* pDexTypeId;
692 const char* classDescr;
693
694 pDexTypeId = dexGetTypeId(pDexFile, i);
695 classDescr = dexStringById(pDexFile, pDexTypeId->descriptorIdx);
696
697 if (dvmIsBitSet(pResults->usedStrings, i))
698 printf("used : %04x '%s'\n", i, classDescr);
699 else
700 printf("unused: %04x '%s'\n", i, classDescr);
701 }
702#endif
703#if 0
704 for (i = 0; i < (int) pDvmDex->pDexFile->pHeader->methodIdsSize; i++) {
705 const DexMethodId* pDexMethodId;
706 const DexTypeId* pDexTypeId;
707 const char* classDescr;
708 const char* methodName;
709
710 pDexMethodId = dexGetMethodId(pDexFile, i);
711 methodName = dexStringById(pDexFile, pDexMethodId->nameIdx);
712
713 pDexTypeId = dexGetTypeId(pDexFile, pDexMethodId->classIdx);
714 classDescr = dexStringById(pDexFile, pDexTypeId->descriptorIdx);
715 if (dvmIsBitSet(pResults->usedMethods, i))
716 printf("used : %s.%s\n", classDescr, methodName);
717 else
718 printf("unused: %s.%s\n", classDescr, methodName);
719 }
720#endif
721#if 0
722 for (i = 0; i < (int) pDvmDex->pDexFile->pHeader->fieldIdsSize; i++) {
723 const DexFieldId* pDexFieldId;
724 const DexTypeId* pDexTypeId;
725 const char* classDescr;
726 const char* fieldName;
727
728 pDexFieldId = dexGetFieldId(pDexFile, i);
729 fieldName = dexStringById(pDexFile, pDexFieldId->nameIdx);
730
731 pDexTypeId = dexGetTypeId(pDexFile, pDexFieldId->classIdx);
732 classDescr = dexStringById(pDexFile, pDexTypeId->descriptorIdx);
733 if (dvmIsBitSet(pResults->usedFields, i))
734 printf("used : %s.%s\n", classDescr, fieldName);
735 else
736 printf("unused: %s.%s\n", classDescr, fieldName);
737 }
738#endif
739#if 0
740 for (i = 0; i < (int) pDvmDex->pDexFile->pHeader->stringIdsSize; i++) {
741 const char* str;
742
743 str = dexStringById(pDexFile, i);
744
745 if (dvmIsBitSet(pResults->usedStrings, i))
746 printf("used : %04x '%s'\n", i, str);
747 else
748 printf("unused: %04x '%s'\n", i, str);
749 }
750#endif
751
752 int totalMax, totalSet;
753 int setCount;
754
755 totalMax = totalSet = 0;
756
757 setCount = dvmCountSetBits(pResults->usedClasses);
758 showBitCount("classes", setCount, pDexFile->pHeader->typeIdsSize);
759 totalSet += setCount;
760 totalMax += pDexFile->pHeader->typeIdsSize;
761
762 setCount = dvmCountSetBits(pResults->usedMethods);
763 showBitCount("methods", setCount, pDexFile->pHeader->methodIdsSize);
764 totalSet += setCount;
765 totalMax += pDexFile->pHeader->methodIdsSize;
766
767 setCount = dvmCountSetBits(pResults->usedFields);
768 showBitCount("fields", setCount, pDexFile->pHeader->fieldIdsSize);
769 totalSet += setCount;
770 totalMax += pDexFile->pHeader->fieldIdsSize;
771
772 setCount = dvmCountSetBits(pResults->usedStrings);
773 showBitCount("strings", setCount, pDexFile->pHeader->stringIdsSize);
774 totalSet += setCount;
775 totalMax += pDexFile->pHeader->stringIdsSize;
776
777 printf("TOTAL %d of %d (%.1f%% unused -- %.1fK)\n", totalSet, totalMax,
778 ((totalMax - totalSet) * 100.0f) / totalMax,
779 (totalMax - totalSet) / 256.0f);
780}
781
782/*
783 * Fill out an index map set entry.
784 *
785 * If we can't fit the map into our base type, we don't create the map.
786 *
787 * Returns "false" if allocation fails.
788 */
789static bool constructIndexMap(int totalCount, const BitVector* pBits,
790 IndexMap* pMap)
791{
792 const int kMaxIndex = 65534; // 65535, a/k/a -1, is special
793 int setCount;
794
795 setCount = dvmCountSetBits(pBits);
796 if (setCount < 0 || setCount > kMaxIndex)
797 return true;
798
799 u2* mapToOld = (u2*) malloc(setCount * sizeof(u2));
800 u2* mapToNew = (u2*) malloc(totalCount * sizeof(u2));
801 if (mapToOld == NULL || mapToNew == NULL) {
802 free(mapToOld);
803 free(mapToNew);
804 return false;
805 }
806
807 /* fill in both arrays */
808 int entry, idx = 0;
809 for (entry = 0; entry < totalCount; entry++) {
810 if (dvmIsBitSet(pBits, entry)) {
811 mapToNew[entry] = idx;
812 mapToOld[idx] = entry;
813 idx++;
814 } else {
815 mapToNew[entry] = kNoIndexMapping;
816 }
817 }
818
819 if (idx != setCount) {
820 LOGE("GLITCH: idx=%d setCount=%d\n", idx, setCount);
821 dvmAbort();
822 }
823
824 /* success */
825 pMap->mapToOld = mapToOld;
826 pMap->mapToNew = mapToNew;
827 pMap->origCount = totalCount;
828 pMap->newCount = setCount;
829
830 return true;
831}
832
833/*
834 * Construct a "reducing" chunk, with maps that convert the constants in
835 * instructions to their reduced value for the cache lookup.
836 */
837static bool constructReducingDataChunk(IndexMapSet* pIndexMapSet)
838{
839 int chunkLen = 0;
840 int i;
841
842 pIndexMapSet->chunkType = kDexChunkReducingIndexMap;
843
844 /*
845 * Compute space requirements and allocate storage.
846 */
847 for (i = 0; i < kNumIndexMaps; i++) {
848 /* space for the "original" count */
849 chunkLen += sizeof(u4);
850
851 /* space for the "reduced" count */
852 chunkLen += sizeof(u4);
853
854 /* add data length, round up to 32-bit boundary */
855 chunkLen += pIndexMapSet->map[i].origCount * sizeof(u2);
856 chunkLen = (chunkLen + 3) & ~3;
857 }
858
859 pIndexMapSet->chunkDataLen = chunkLen;
860 pIndexMapSet->chunkData = (u1*) calloc(1, chunkLen);
861 if (pIndexMapSet->chunkData == NULL)
862 return false;
863
864 /*
865 * Copy the data in.
866 */
867 u1* ptr = pIndexMapSet->chunkData;
868 for (i = 0; i < kNumIndexMaps; i++) {
869 u4* wordPtr = (u4*) ptr;
870 int dataLen = pIndexMapSet->map[i].origCount * sizeof(u2);
871
872 *wordPtr++ = pIndexMapSet->map[i].origCount;
873 *wordPtr++ = pIndexMapSet->map[i].newCount;
874 if (dataLen != 0)
875 memcpy(wordPtr, pIndexMapSet->map[i].mapToNew, dataLen);
876
877 /* advance pointer, maintaining 32-bit alignment */
878 ptr = ((u1*) wordPtr) + dataLen;
879 ptr = (u1*) (((int) ptr + 3) & ~3);
880 }
881
882 if (ptr - (u1*) pIndexMapSet->chunkData != chunkLen) {
883 LOGE("GLITCH: expected len=%d, actual=%d\n",
884 chunkLen, ptr - (u1*) pIndexMapSet->chunkData);
885 dvmAbort();
886 }
887
888 return true;
889}
890
891/*
892 * Construct an "expanding" chunk, with maps that convert instructions
893 * with reduced constants back to their full original values.
894 */
Carl Shapiroe3c01da2010-05-20 22:54:18 -0700895#if DVM_RESOLVER_CACHE == DVM_RC_EXPANDING
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800896static bool constructExpandingDataChunk(IndexMapSet* pIndexMapSet)
897{
898 int chunkLen = 0;
899 int i;
900
901 pIndexMapSet->chunkType = kDexChunkExpandingIndexMap;
902
903 /*
904 * Compute space requirements and allocate storage.
905 */
906 for (i = 0; i < kNumIndexMaps; i++) {
907 /* space for the length word */
908 chunkLen += sizeof(u4);
909
910 /* add data length, round up to 32-bit boundary */
911 chunkLen += pIndexMapSet->map[i].newCount * sizeof(u2);
912 chunkLen = (chunkLen + 3) & ~3;
913 }
914
915 pIndexMapSet->chunkDataLen = chunkLen;
916 pIndexMapSet->chunkData = (u1*) calloc(1, chunkLen);
917 if (pIndexMapSet->chunkData == NULL)
918 return false;
919
920 /*
921 * Copy the data in.
922 */
923 u1* ptr = pIndexMapSet->chunkData;
924 for (i = 0; i < kNumIndexMaps; i++) {
925 u4* wordPtr = (u4*) ptr;
926 int dataLen = pIndexMapSet->map[i].newCount * sizeof(u2);
927
928 *wordPtr++ = pIndexMapSet->map[i].newCount;
929 if (dataLen != 0)
930 memcpy(wordPtr, pIndexMapSet->map[i].mapToOld, dataLen);
931
932 /* advance pointer, maintaining 32-bit alignment */
933 ptr = ((u1*) wordPtr) + dataLen;
934 ptr = (u1*) (((int) ptr + 3) & ~3);
935 }
936
937 if (ptr - (u1*) pIndexMapSet->chunkData != chunkLen) {
938 LOGE("GLITCH: expected len=%d, actual=%d\n",
939 chunkLen, ptr - (u1*) pIndexMapSet->chunkData);
940 dvmAbort();
941 }
942
943 return true;
944}
Carl Shapiroe3c01da2010-05-20 22:54:18 -0700945#endif
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800946
947/*
948 * Construct the "chunk" of data that will be appended to the optimized DEX
949 * file.
950 */
951static bool constructDataChunk(IndexMapSet* pIndexMapSet)
952{
953 assert(sizeof(pIndexMapSet->map[0].mapToOld[0]) == sizeof(u2));
954 assert(sizeof(pIndexMapSet->map[0].mapToNew[0]) == sizeof(u2));
955
956#if DVM_RESOLVER_CACHE == DVM_RC_EXPANDING
957 return constructExpandingDataChunk(pIndexMapSet);
958#else
959 return constructReducingDataChunk(pIndexMapSet);
960#endif
961}
962
963/*
964 * Allocate storage to hold the maps.
965 */
966static IndexMapSet* createIndexMapSet(const DexFile* pDexFile,
967 ScanResults* pResults)
968{
969 IndexMapSet* pIndexMapSet;
The Android Open Source Projectf6c38712009-03-03 19:28:47 -0800970 bool okay = true;
971
972 pIndexMapSet = calloc(1, sizeof(*pIndexMapSet));
973 if (pIndexMapSet == NULL)
974 return NULL;
975
976 okay = okay && constructIndexMap(pDexFile->pHeader->typeIdsSize,
977 pResults->usedClasses, &pIndexMapSet->map[kMapClasses]);
978 okay = okay && constructIndexMap(pDexFile->pHeader->methodIdsSize,
979 pResults->usedMethods, &pIndexMapSet->map[kMapMethods]);
980 okay = okay && constructIndexMap(pDexFile->pHeader->fieldIdsSize,
981 pResults->usedFields, &pIndexMapSet->map[kMapFields]);
982 okay = okay && constructIndexMap(pDexFile->pHeader->stringIdsSize,
983 pResults->usedStrings, &pIndexMapSet->map[kMapStrings]);
984
985 LOGVV("Constr: %d %d %d %d\n",
986 pIndexMapSet->map[kMapClasses].mapToOld[0],
987 pIndexMapSet->map[kMapMethods].mapToOld[0],
988 pIndexMapSet->map[kMapFields].mapToOld[0],
989 pIndexMapSet->map[kMapStrings].mapToOld[0]);
990
991 okay = okay && constructDataChunk(pIndexMapSet);
992
993 if (!okay) {
994 dvmFreeIndexMapSet(pIndexMapSet);
995 return NULL;
996 }
997
998 return pIndexMapSet;
999}
1000
1001/*
1002 * Free map storage.
1003 *
1004 * "pIndexMapSet" may be incomplete.
1005 */
1006void dvmFreeIndexMapSet(IndexMapSet* pIndexMapSet)
1007{
1008 int i;
1009
1010 if (pIndexMapSet == NULL)
1011 return;
1012
1013 for (i = 0; i < kNumIndexMaps; i++) {
1014 free(pIndexMapSet->map[i].mapToOld);
1015 free(pIndexMapSet->map[i].mapToNew);
1016 }
1017 free(pIndexMapSet->chunkData);
1018 free(pIndexMapSet);
1019}
1020
1021/*
1022 * Rewrite constant indexes to reduce heap requirements.
1023 */
1024IndexMapSet* dvmRewriteConstants(DvmDex* pDvmDex)
1025{
1026#if (DVM_RESOLVER_CACHE != DVM_RC_REDUCING) && \
1027 (DVM_RESOLVER_CACHE != DVM_RC_EXPANDING)
1028 /* nothing to do */
1029 return NULL;
1030#endif
1031
1032 /*
1033 * We're looking for instructions that use "constant pool" entries for
1034 * classes, methods, fields, and strings. Many field and method entries
1035 * are optimized away, and many string constants are never accessed from
1036 * code or annotations.
1037 */
1038 ScanResults* pResults = allocScanResults(pDvmDex->pDexFile);
1039 forAllMethods(pDvmDex->pDexFile, markUsedConstants, pResults);
1040
1041 summarizeResults(pDvmDex, pResults);
1042
1043 /*
1044 * Allocate and populate the index maps.
1045 */
1046 IndexMapSet* pIndexMapSet = createIndexMapSet(pDvmDex->pDexFile, pResults);
1047#if DVM_RESOLVER_CACHE == DVM_RC_EXPANDING
1048 if (pIndexMapSet != NULL) {
1049 /*
1050 * Rewrite the constants to use the reduced set.
1051 */
1052 forAllMethods(pDvmDex->pDexFile, updateUsedConstants, pIndexMapSet);
1053 }
1054#endif
1055
1056 freeScanResults(pResults);
1057
1058 return pIndexMapSet;
1059}