+++ /dev/null
-/*
- * Copyright (C) 2008 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.
- */
-
-/*
- * Compress the range of "constant pool" indexes in instructions and
- * annotations to lower runtime RAM footprint.
- *
- * NOTE: this is an incomplete experimental feature. Do not try to use it.
- */
-#include "Dalvik.h"
-#include "libdex/InstrUtils.h"
-#include "libdex/OptInvocation.h"
-#include "libdex/DexClass.h"
-
-/*
-Overview
-
-When a class, method, field, or string constant is referred to from
-Dalvik bytecode, the reference takes the form of an integer index value.
-This value indexes into an array of type_id_item, method_id_item,
-field_id_item, or string_id_item in the DEX file. The first three
-themselves contain (directly or indirectly) indexes to strings that the
-resolver uses to convert the instruction stream index into a pointer to
-the appropriate object or struct.
-
-For example, an invoke-virtual instruction needs to specify which method
-is to be invoked. The method constant indexes into the method_id_item
-array, each entry of which has indexes that specify the defining class
-(type_id_item), method name (string_id_item), and method prototype
-(proto_id_item). The type_id_item just holds an index to a string_id_item,
-which holds the file offset to the string with the class name. The VM
-finds the class by name, then searches through the class' table of virtual
-methods to find one with a matching name and prototype.
-
-This process is fairly expensive, so after the first time it completes
-successfully, the VM records that the method index resolved to a specific
-Method struct. On subsequent execution, the VM just pulls the Method ptr
-out of the resolved-methods array. A similar approach is used with
-the indexes for classes, fields, and string constants.
-
-The problem with this approach is that we need to have a "resolved" entry
-for every possible class, method, field, and string constant in every
-DEX file, even if some of those aren't used from code. The DEX string
-constant table has entries for method prototypes and class names that are
-never used by the code, and "public static final" fields often turn into
-immediate constants. The resolution table entries are only 4 bytes each,
-but there are roughly 200,000 of them in the bootstrap classes alone.
-
-DEX optimization removes many index references by replacing virtual method
-indexes with vtable offsets and instance field indexes with byte offsets.
-In the earlier example, the method would be resolved at "dexopt" time, and
-the instruction rewritten as invoke-virtual-quick with the vtable offset.
-
-(There are comparatively few classes compared to other constant pool
-entries, and a much higher percentage (typically 60-70%) are used. The
-biggest gains come from the string pool.)
-
-Using the resolved-entity tables provides a substantial performance
-improvement, but results in applications allocating 1MB+ of tables that
-are 70% unused. The used and unused entries are freely intermixed,
-preventing effective sharing with the zygote process, and resulting in
-large numbers of private/dirty pages on the native heap as the tables
-populate on first use.
-
-The trick is to reduce the memory usage without decreasing performance.
-Using smaller resolved-entity tables can actually give us a speed boost,
-because we'll have a smaller "live" set of pages and make more effective
-use of the data cache.
-
-
-The approach we're going to use is to determine the set of indexes that
-could potentially be resolved, generate a mapping from the minimal set to
-the full set, and append the mapping to the DEX file. This is done at
-"dexopt" time, because we need to keep the changes in shared/read-only
-pages or we'll lose the benefits of doing the work.
-
-There are two ways to create and use the new mapping:
-
- (1) Write the entire full->minimal mapping to the ".odex" file. On every
- instruction that uses an index, use the mapping to determine the
- "compressed" constant value, and then use that to index into the
- resolved-entity tables on the heap. The instruction stream is unchanged,
- and the resolver can easily tell if a given index is cacheable.
-
- (2) Write the inverse miminal->full mapping to the ".odex" file, and
- rewrite the constants in the instruction stream. The interpreter is
- unchanged, and the resolver code uses the mapping to find the original
- data in the DEX.
-
-Approach #1 is easier and safer to implement, but it requires a table
-lookup every time we execute an instruction that includes a constant
-pool reference. This causes an unacceptable performance hit, chiefly
-because we're hitting semi-random memory pages and hosing the data cache.
-This is mitigated somewhat by DEX optimizations that replace the constant
-with a vtable index or field byte offset. Approach #1 also requires
-a larger map table, increasing the size of the DEX on disk. One nice
-property of approach #1 is that most of the DEX file is unmodified,
-so use of the mapping is a runtime decision.
-
-Approach #2 is preferred for performance reasons.
-
-
-The class/method/field/string resolver code has to handle indices from
-three sources: interpreted instructions, annotations, and exception
-"catch" lists. Sometimes these occur indirectly, e.g. we need to resolve
-the declaring class associated with fields and methods when the latter
-two are themselves resolved. Parsing and rewriting instructions is fairly
-straightforward, but annotations use a complex format with variable-width
-index values.
-
-We can safely rewrite index values in annotations if we guarantee that the
-new value is smaller than the original. This implies a two-pass approach:
-the first determines the set of indexes actually used, the second does the
-rewrite. Doing the rewrite in a single pass would be much harder.
-
-Instances of the "original" indices will still be found in the file; if
-we try to be all-inclusive we will include some stuff that doesn't need
-to be there (e.g. we don't generally need to cache the class name string
-index result, since once we have the class resolved we don't need to look
-it up by name through the resolver again). There is some potential for
-performance improvement by caching more than we strictly need, but we can
-afford to give up a little performance during class loading if it allows
-us to regain some memory.
-
-For safety and debugging, it's useful to distinguish the "compressed"
-constants in some way, e.g. setting the high bit when we rewrite them.
-In practice we don't have any free bits: indexes are usually 16-bit
-values, and we have more than 32,767 string constants in at least one of
-our core DEX files. Also, this does not work with constants embedded in
-annotations, because of the variable-width encoding.
-
-We should be safe if we can establish a clear distinction between sources
-of "original" and "compressed" indices. If the values get crossed up we
-can end up with elusive bugs. The easiest approach is to declare that
-only indices pulled from certain locations (the instruction stream and/or
-annotations) are compressed. This prevents us from adding indices in
-arbitrary locations to the compressed set, but should allow a reasonably
-robust implementation.
-
-
-Further implementation thoughts:
-
- - We don't have to do annotations in the first pass. At heart the
- resolved entity cache is a performance optimization, not necessary for
- correctness, and we're not making annotation performance a priority
- at this stage.
- - The most important "fast path" is instruction processing. Everything
- else can do additional work without having a measurable impact.
- However...
- - We need to keep an eye on uncached resolves to ensure that we haven't
- introduced noticeable performance losses. In particular, the use of
- runtime annotations with string constants may suffer if we don't include
- annotation rewriting in the solution.
- - We can have separate resolver functions for "original" and "compressed"
- indices. This way we don't have to add a flag argument to the resolver
- functions (which would require passing an additional parameter in from
- the interpreter).
- - The VM spec has some specific things to say about string constant
- equality and interning. Index compression should have no effect on
- that; we just change how long it takes to find the interned string in
- certain circumstances. The impact can be mitigated somewhat by
- improving the performance of the interned string table code.
- - This can make e.g. method resolution slower. The method_id_item has
- an index to a method name string, and we will no longer cache the
- result of resolving that string. This impacts resolution of any method
- with the same name as a previously-resolved method.
- - We may need to tweak the tools, particularly "dexdump", to show the
- translated values.
- - We can use 16-bit values in the mapping table, since we should have
- fewer than 2^16 remapped entries. If we overflow we can skip the remap
- for that table or for the entire DEX file. The resolver will need to
- check for the existence of the table to determine whether or not entries
- must be remapped. The cost of the extra check is acceptable for
- approach #2, since it's only at resolve time, but may be undesirable
- for approach #1.
-*/
-/*
-Output Formats
-
-There are two possible output formats, from which we choose based on how
-we plan to take advantage of the remapped constants. At most one of these
-will appear in the DEX.
-
-NOTE: if EIXM appears in the DEX, the VM *must* be configured with
-DVM_RESOLVER_CACHE=DVM_RC_EXPANDING (2). Otherwise the constants we
-pull from the instruction stream will be wrong and we will fail quickly.
-
-For approach #1: map from original indices to the reduced set.
-
- This includes the four "mapToNew" tables.
-
- Format (RIXM):
- u4 classCount // #of entries in classMap[]; == typeIdsSize
- u4 reducedClassCount // #of entries in remapped table (for alloc)
- u2 classMap[]
- u4 methodCount
- u4 reducedMethodCount
- u2 methodMap[]
- u4 fieldCount
- u4 reducedFieldCount
- u2 fieldMap[]
- u4 stringCount
- u4 reducedStringCount
- u2 stringMap[]
-
-For approach #2: map from the reduced set back to the originals.
-
- This includes the four "mapToOld" tables.
-
- Format (EIXM):
- u4 classCount // #of entries in classMap[]; post-reduction
- u2 classMap[]
- u4 methodCount
- u2 methodMap[]
- u4 fieldCount
- u2 fieldMap[]
- u4 stringCount
- u2 stringMap[]
-
-The arrays are padded so that the "count" values are always aligned on
-32-bit boundaries. All multi-byte values are in native host order.
-*/
-
-
-/*
- * Gather results from the post-optimization instruction scan.
- */
-typedef struct ScanResults {
- /* output */
- BitVector* usedClasses;
- BitVector* usedMethods;
- BitVector* usedFields;
- BitVector* usedStrings;
-} ScanResults;
-
-/* prototype for the for-all-methods function */
-typedef void (AllMethodsFunc)(DexFile* pDexFile, const char* classDescriptor,
- DexMethod* pDexMethod, void* arg);
-
-
-/*
- * Free scan results.
- */
-static void freeScanResults(ScanResults* pResults)
-{
- if (pResults == NULL)
- return;
-
- dvmFreeBitVector(pResults->usedClasses);
- dvmFreeBitVector(pResults->usedMethods);
- dvmFreeBitVector(pResults->usedFields);
- dvmFreeBitVector(pResults->usedStrings);
- free(pResults);
-}
-
-/*
- * Allocate storage for the results of the instruction scan.
- */
-static ScanResults* allocScanResults(const DexFile* pDexFile)
-{
- ScanResults* pResults;
- const DexHeader* pHeader = pDexFile->pHeader;
-
- pResults = (ScanResults*) calloc(1, sizeof(ScanResults));
- if (pResults == NULL)
- return NULL;
-
- pResults->usedClasses = dvmAllocBitVector(pHeader->typeIdsSize, false);
- pResults->usedMethods = dvmAllocBitVector(pHeader->methodIdsSize, false);
- pResults->usedFields = dvmAllocBitVector(pHeader->fieldIdsSize, false);
- pResults->usedStrings = dvmAllocBitVector(pHeader->stringIdsSize, false);
-
- if (pResults->usedClasses == NULL ||
- pResults->usedMethods == NULL ||
- pResults->usedFields == NULL ||
- pResults->usedStrings == NULL)
- {
- freeScanResults(pResults);
- return NULL;
- }
-
- return pResults;
-}
-
-/*
- * Call "func(method, arg)" on all methods in the specified class.
- *
- * Pass in a pointer to the class_data_item, positioned at the start of
- * the field data (i.e. just past the class data header).
- *
- * "classDescriptor" is for debug messages.
- */
-static void forAllMethodsInClass(DexFile* pDexFile, const u1** ppEncodedData,
- const DexClassDataHeader* pHeader, const char* classDescriptor,
- AllMethodsFunc func, void* arg)
-{
- int i;
-
- /*
- * Consume field data.
- */
- if (pHeader->staticFieldsSize != 0) {
- int count = (int) pHeader->staticFieldsSize;
- u4 lastIndex = 0;
- DexField field;
- for (i = 0; i < count; i++) {
- dexReadClassDataField(ppEncodedData, &field, &lastIndex);
- }
- }
- if (pHeader->instanceFieldsSize != 0) {
- int count = (int) pHeader->instanceFieldsSize;
- u4 lastIndex = 0;
- DexField field;
- for (i = 0; i < count; i++) {
- dexReadClassDataField(ppEncodedData, &field, &lastIndex);
- }
- }
-
- /*
- * Run through all methods.
- */
- if (pHeader->directMethodsSize != 0) {
- int count = (int) pHeader->directMethodsSize;
- u4 lastIndex = 0;
- DexMethod method;
-
- for (i = 0; i < count; i++) {
- dexReadClassDataMethod(ppEncodedData, &method, &lastIndex);
- (func)(pDexFile, classDescriptor, &method, arg);
- }
- }
- if (pHeader->virtualMethodsSize != 0) {
- int count = (int) pHeader->virtualMethodsSize;
- u4 lastIndex = 0;
- DexMethod method;
-
- for (i = 0; i < count; i++) {
- dexReadClassDataMethod(ppEncodedData, &method, &lastIndex);
- (func)(pDexFile, classDescriptor, &method, arg);
- }
- }
-}
-
-/*
- * Call "func(method, arg)" on all methods in all classes in DexFile.
- */
-static void forAllMethods(DexFile* pDexFile, AllMethodsFunc func, void* arg)
-{
- u4 count = pDexFile->pHeader->classDefsSize;
- u4 idx;
-
- for (idx = 0; idx < count; idx++) {
- const DexClassDef* pClassDef;
- DexClassDataHeader header;
- const u1* pEncodedData;
-
- pClassDef = dexGetClassDef(pDexFile, idx);
- pEncodedData = dexGetClassData(pDexFile, pClassDef);
-
- const char* classDescriptor;
- classDescriptor = dexStringByTypeIdx(pDexFile, pClassDef->classIdx);
-
- if (pEncodedData != NULL) {
- dexReadClassDataHeader(&pEncodedData, &header);
-
- forAllMethodsInClass(pDexFile, &pEncodedData, &header,
- classDescriptor, func, arg);
- } else {
- //printf("%s: no class data\n", classDescriptor);
- /* no class data, e.g. "marker interface" */
- }
- }
-}
-
-/*
- * Mark a class ID as referenced.
- */
-static void markClass(const u2* ptr, ScanResults* pResults)
-{
- u2 classIdx = *ptr;
- if (!dvmSetBit(pResults->usedClasses, classIdx)) {
- LOGE("Unable to mark class %d as in-use\n", classIdx);
- }
-}
-
-/*
- * Mark a method ID as referenced.
- */
-static void markMethod(const u2* ptr, ScanResults* pResults)
-{
- u2 methodIdx = *ptr;
- if (!dvmSetBit(pResults->usedMethods, methodIdx)) {
- LOGE("Unable to mark method %d as in-use\n", methodIdx);
- }
-}
-
-/*
- * Mark a field ID as referenced.
- */
-static void markField(const u2* ptr, ScanResults* pResults)
-{
- u2 fieldIdx = *ptr;
- if (!dvmSetBit(pResults->usedFields, fieldIdx)) {
- LOGE("Unable to mark field %d as in-use\n", fieldIdx);
- }
-}
-
-/*
- * Mark a string constant as referenced.
- */
-static void markString(const u2* ptr, ScanResults* pResults)
-{
- u2 stringIdx = *ptr;
- if (!dvmSetBit(pResults->usedStrings, stringIdx)) {
- LOGE("Unable to mark string %d as in-use\n", stringIdx);
- }
-}
-
-/*
- * Mark a "jumbo" string constant as referenced.
- */
-static void markJumboString(u2* ptr, ScanResults* pResults)
-{
- u4 stringIdx;
-
- /* it's in native byte order, but might not be 32-bit aligned */
- memcpy(&stringIdx, ptr, sizeof(u4));
- if (!dvmSetBit(pResults->usedStrings, stringIdx)) {
- LOGE("Unable to mark string %d as in-use\n", stringIdx);
- }
-}
-
-/*
- * Remap a value in the instruction stream.
- */
-static inline void updateValue(u2* ptr, const IndexMapSet* pIndexMapSet,
- int whichMap)
-{
- const IndexMap* pMap = &pIndexMapSet->map[whichMap];
- if (pMap != NULL) {
- u2 newIdx = pMap->mapToNew[*ptr];
- assert(newIdx != kNoIndexMapping);
- *ptr = newIdx;
- }
-}
-static void updateClass(u2* ptr, const IndexMapSet* pIndexMapSet)
-{
- updateValue(ptr, pIndexMapSet, kMapClasses);
-}
-static void updateMethod(u2* ptr, const IndexMapSet* pIndexMapSet)
-{
- updateValue(ptr, pIndexMapSet, kMapMethods);
-}
-static void updateField(u2* ptr, const IndexMapSet* pIndexMapSet)
-{
- updateValue(ptr, pIndexMapSet, kMapFields);
-}
-static void updateString(u2* ptr, const IndexMapSet* pIndexMapSet)
-{
- updateValue(ptr, pIndexMapSet, kMapStrings);
-}
-static void updateJumboString(u2* ptr, const IndexMapSet* pIndexMapSet)
-{
- u4 stringIdx;
- u4 newIdx;
-
- /* it's in native byte order, but might not be 32-bit aligned */
- memcpy(&stringIdx, ptr, sizeof(stringIdx));
-
- /* get new value */
- newIdx = pIndexMapSet->map[kMapStrings].mapToNew[*ptr];
- assert(newIdx != kNoIndexMapping);
-
- /* copy it out */
- memcpy(ptr, &newIdx, sizeof(newIdx));
-}
-
-/*
- * Run through an instructions stream, marking constants as we see them.
- *
- * If "pResults" is non-NULL, we populate "pResults" with what we find,
- * making no changes to the instruction stream.
- *
- * If "pIndexMapSet" is non-NULL, we rewrite the constants in the
- * instruction stream.
- */
-static void markUsedConstantsFromInsns(u2* insns, u4 insnsSize,
- ScanResults* pResults, const IndexMapSet* pIndexMapSet)
-{
- //printf(" %p %u units\n", insns, insnsSize);
-
- while (insnsSize > 0) {
- int width;
- u2* pConst = insns + 1;
-
- switch (*insns & 0xff) {
- case OP_IGET:
- case OP_IGET_WIDE:
- case OP_IGET_OBJECT:
- case OP_IGET_BOOLEAN:
- case OP_IGET_BYTE:
- case OP_IGET_CHAR:
- case OP_IGET_SHORT:
- case OP_IPUT:
- case OP_IPUT_WIDE:
- case OP_IPUT_OBJECT:
- case OP_IPUT_BOOLEAN:
- case OP_IPUT_BYTE:
- case OP_IPUT_CHAR:
- case OP_IPUT_SHORT:
- case OP_SGET:
- case OP_SGET_WIDE:
- case OP_SGET_OBJECT:
- case OP_SGET_BOOLEAN:
- case OP_SGET_BYTE:
- case OP_SGET_CHAR:
- case OP_SGET_SHORT:
- case OP_SPUT:
- case OP_SPUT_WIDE:
- case OP_SPUT_OBJECT:
- case OP_SPUT_BOOLEAN:
- case OP_SPUT_BYTE:
- case OP_SPUT_CHAR:
- case OP_SPUT_SHORT:
- /* instanceop vA, vB, field@CCCC */
- /* staticop vAA, field@BBBB */
- if (pResults != NULL)
- markField(pConst, pResults);
- else
- updateField(pConst, pIndexMapSet);
- break;
-
- case OP_CONST_STRING:
- /* const-string vAA, string@BBBB */
- if (pResults != NULL)
- markString(pConst, pResults);
- else
- updateString(pConst, pIndexMapSet);
- break;
-
- case OP_CONST_STRING_JUMBO:
- /* const-string/jumbo vAA, string@BBBBBBBB */
- if (pResults != NULL)
- markJumboString(pConst, pResults);
- else
- updateJumboString(pConst, pIndexMapSet);
- break;
-
- case OP_CONST_CLASS:
- case OP_CHECK_CAST:
- case OP_NEW_INSTANCE:
- case OP_FILLED_NEW_ARRAY_RANGE:
- case OP_INSTANCE_OF:
- case OP_NEW_ARRAY:
- case OP_FILLED_NEW_ARRAY:
- /* const-class vAA, type@BBBB */
- /* check-cast vAA, type@BBBB */
- /* new-instance vAA, type@BBBB */
- /* filled-new-array/range {vCCCC .. vNNNN}, type@BBBB */
- /* instance-of vA, vB, type@CCCC */
- /* new-array vA, vB, type@CCCC */
- /* filled-new-array {vD, vE, vF, vG, vA}, type@CCCC */
- if (pResults != NULL)
- markClass(pConst, pResults);
- else
- updateClass(pConst, pIndexMapSet);
- break;
-
- case OP_INVOKE_VIRTUAL:
- case OP_INVOKE_SUPER:
- case OP_INVOKE_DIRECT:
- case OP_INVOKE_STATIC:
- case OP_INVOKE_INTERFACE:
- case OP_INVOKE_VIRTUAL_RANGE:
- case OP_INVOKE_SUPER_RANGE:
- case OP_INVOKE_DIRECT_RANGE:
- case OP_INVOKE_STATIC_RANGE:
- case OP_INVOKE_INTERFACE_RANGE:
- /* invoke-kind {vD, vE, vF, vG, vA}, meth@CCCC */
- /* invoke-kind/range {vCCCC .. vNNNN}, meth@BBBB */
- if (pResults != NULL)
- markMethod(pConst, pResults);
- else
- updateMethod(pConst, pIndexMapSet);
- break;
-
- default:
- // ignore this instruction
- ;
- }
-
- width = dexGetInstrOrTableWidthAbs(gDvm.instrWidth, insns);
- assert(width > 0 && width <= (int)insnsSize);
-
- insns += width;
- insnsSize -= width;
- }
-}
-
-/*
- * This is an AllMethodsFunc implementation.
- *
- * Run through the instructions in this method, setting bits in the "pResults"
- * struct as we locate constants.
- */
-static void markUsedConstants(DexFile* pDexFile, const char* classDescriptor,
- DexMethod* pDexMethod, void* arg)
-{
- ScanResults* pResults = (ScanResults*) arg;
- const DexCode* pDexCode = dexGetCode(pDexFile, pDexMethod);
-
- if (false) {
- const DexMethodId* pMethodId;
- const char* methodName;
- pMethodId = dexGetMethodId(pDexFile, pDexMethod->methodIdx);
- methodName = dexStringById(pDexFile, pMethodId->nameIdx);
- printf(" %s.%s\n", classDescriptor, methodName);
- }
-
- if (pDexCode != NULL) {
- u2* insns = (u2*) pDexCode->insns;
- u4 insnsSize = pDexCode->insnsSize;
- markUsedConstantsFromInsns(insns, insnsSize, pResults, NULL);
- } else {
- //printf(" (no code)\n");
- }
-}
-
-/*
- * This is an AllMethodsFunc implementation.
- *
- * Run through the instructions in this method, altering the constants used.
- */
-#if DVM_RESOLVER_CACHE == DVM_RC_EXPANDING
-static void updateUsedConstants(DexFile* pDexFile, const char* classDescriptor,
- DexMethod* pDexMethod, void* arg)
-{
- const IndexMapSet* pIndexMapSet = (const IndexMapSet*) arg;
- const DexCode* pDexCode = dexGetCode(pDexFile, pDexMethod);
-
- if (false) {
- const DexMethodId* pMethodId;
- const char* methodName;
- pMethodId = dexGetMethodId(pDexFile, pDexMethod->methodIdx);
- methodName = dexStringById(pDexFile, pMethodId->nameIdx);
- printf(" %s.%s\n", classDescriptor, methodName);
- }
-
- if (pDexCode != NULL) {
- u2* insns = (u2*) pDexCode->insns;
- u4 insnsSize = pDexCode->insnsSize;
- markUsedConstantsFromInsns(insns, insnsSize, NULL, pIndexMapSet);
- } else {
- //printf(" (no code)\n");
- }
-}
-#endif
-
-/*
- * Count up the bits and show a count.
- */
-static void showBitCount(const char* label, int setCount, int maxCount)
-{
- printf("%s: %d of %d (%.1f%% unused)\n", label, setCount, maxCount,
- ((maxCount - setCount) * 100.0f) / maxCount);
-}
-
-/*
- * Print some debug info.
- */
-static void summarizeResults(DvmDex* pDvmDex, ScanResults* pResults)
-{
- DexFile* pDexFile = pDvmDex->pDexFile;
-#if 0
- int i;
-
- for (i = 0; i < (int) pDvmDex->pDexFile->pHeader->typeIdsSize; i++) {
- const DexTypeId* pDexTypeId;
- const char* classDescr;
-
- pDexTypeId = dexGetTypeId(pDexFile, i);
- classDescr = dexStringById(pDexFile, pDexTypeId->descriptorIdx);
-
- if (dvmIsBitSet(pResults->usedStrings, i))
- printf("used : %04x '%s'\n", i, classDescr);
- else
- printf("unused: %04x '%s'\n", i, classDescr);
- }
-#endif
-#if 0
- for (i = 0; i < (int) pDvmDex->pDexFile->pHeader->methodIdsSize; i++) {
- const DexMethodId* pDexMethodId;
- const DexTypeId* pDexTypeId;
- const char* classDescr;
- const char* methodName;
-
- pDexMethodId = dexGetMethodId(pDexFile, i);
- methodName = dexStringById(pDexFile, pDexMethodId->nameIdx);
-
- pDexTypeId = dexGetTypeId(pDexFile, pDexMethodId->classIdx);
- classDescr = dexStringById(pDexFile, pDexTypeId->descriptorIdx);
- if (dvmIsBitSet(pResults->usedMethods, i))
- printf("used : %s.%s\n", classDescr, methodName);
- else
- printf("unused: %s.%s\n", classDescr, methodName);
- }
-#endif
-#if 0
- for (i = 0; i < (int) pDvmDex->pDexFile->pHeader->fieldIdsSize; i++) {
- const DexFieldId* pDexFieldId;
- const DexTypeId* pDexTypeId;
- const char* classDescr;
- const char* fieldName;
-
- pDexFieldId = dexGetFieldId(pDexFile, i);
- fieldName = dexStringById(pDexFile, pDexFieldId->nameIdx);
-
- pDexTypeId = dexGetTypeId(pDexFile, pDexFieldId->classIdx);
- classDescr = dexStringById(pDexFile, pDexTypeId->descriptorIdx);
- if (dvmIsBitSet(pResults->usedFields, i))
- printf("used : %s.%s\n", classDescr, fieldName);
- else
- printf("unused: %s.%s\n", classDescr, fieldName);
- }
-#endif
-#if 0
- for (i = 0; i < (int) pDvmDex->pDexFile->pHeader->stringIdsSize; i++) {
- const char* str;
-
- str = dexStringById(pDexFile, i);
-
- if (dvmIsBitSet(pResults->usedStrings, i))
- printf("used : %04x '%s'\n", i, str);
- else
- printf("unused: %04x '%s'\n", i, str);
- }
-#endif
-
- int totalMax, totalSet;
- int setCount;
-
- totalMax = totalSet = 0;
-
- setCount = dvmCountSetBits(pResults->usedClasses);
- showBitCount("classes", setCount, pDexFile->pHeader->typeIdsSize);
- totalSet += setCount;
- totalMax += pDexFile->pHeader->typeIdsSize;
-
- setCount = dvmCountSetBits(pResults->usedMethods);
- showBitCount("methods", setCount, pDexFile->pHeader->methodIdsSize);
- totalSet += setCount;
- totalMax += pDexFile->pHeader->methodIdsSize;
-
- setCount = dvmCountSetBits(pResults->usedFields);
- showBitCount("fields", setCount, pDexFile->pHeader->fieldIdsSize);
- totalSet += setCount;
- totalMax += pDexFile->pHeader->fieldIdsSize;
-
- setCount = dvmCountSetBits(pResults->usedStrings);
- showBitCount("strings", setCount, pDexFile->pHeader->stringIdsSize);
- totalSet += setCount;
- totalMax += pDexFile->pHeader->stringIdsSize;
-
- printf("TOTAL %d of %d (%.1f%% unused -- %.1fK)\n", totalSet, totalMax,
- ((totalMax - totalSet) * 100.0f) / totalMax,
- (totalMax - totalSet) / 256.0f);
-}
-
-/*
- * Fill out an index map set entry.
- *
- * If we can't fit the map into our base type, we don't create the map.
- *
- * Returns "false" if allocation fails.
- */
-static bool constructIndexMap(int totalCount, const BitVector* pBits,
- IndexMap* pMap)
-{
- const int kMaxIndex = 65534; // 65535, a/k/a -1, is special
- int setCount;
-
- setCount = dvmCountSetBits(pBits);
- if (setCount < 0 || setCount > kMaxIndex)
- return true;
-
- u2* mapToOld = (u2*) malloc(setCount * sizeof(u2));
- u2* mapToNew = (u2*) malloc(totalCount * sizeof(u2));
- if (mapToOld == NULL || mapToNew == NULL) {
- free(mapToOld);
- free(mapToNew);
- return false;
- }
-
- /* fill in both arrays */
- int entry, idx = 0;
- for (entry = 0; entry < totalCount; entry++) {
- if (dvmIsBitSet(pBits, entry)) {
- mapToNew[entry] = idx;
- mapToOld[idx] = entry;
- idx++;
- } else {
- mapToNew[entry] = kNoIndexMapping;
- }
- }
-
- if (idx != setCount) {
- LOGE("GLITCH: idx=%d setCount=%d\n", idx, setCount);
- dvmAbort();
- }
-
- /* success */
- pMap->mapToOld = mapToOld;
- pMap->mapToNew = mapToNew;
- pMap->origCount = totalCount;
- pMap->newCount = setCount;
-
- return true;
-}
-
-/*
- * Construct a "reducing" chunk, with maps that convert the constants in
- * instructions to their reduced value for the cache lookup.
- */
-static bool constructReducingDataChunk(IndexMapSet* pIndexMapSet)
-{
- int chunkLen = 0;
- int i;
-
- pIndexMapSet->chunkType = kDexChunkReducingIndexMap;
-
- /*
- * Compute space requirements and allocate storage.
- */
- for (i = 0; i < kNumIndexMaps; i++) {
- /* space for the "original" count */
- chunkLen += sizeof(u4);
-
- /* space for the "reduced" count */
- chunkLen += sizeof(u4);
-
- /* add data length, round up to 32-bit boundary */
- chunkLen += pIndexMapSet->map[i].origCount * sizeof(u2);
- chunkLen = (chunkLen + 3) & ~3;
- }
-
- pIndexMapSet->chunkDataLen = chunkLen;
- pIndexMapSet->chunkData = (u1*) calloc(1, chunkLen);
- if (pIndexMapSet->chunkData == NULL)
- return false;
-
- /*
- * Copy the data in.
- */
- u1* ptr = pIndexMapSet->chunkData;
- for (i = 0; i < kNumIndexMaps; i++) {
- u4* wordPtr = (u4*) ptr;
- int dataLen = pIndexMapSet->map[i].origCount * sizeof(u2);
-
- *wordPtr++ = pIndexMapSet->map[i].origCount;
- *wordPtr++ = pIndexMapSet->map[i].newCount;
- if (dataLen != 0)
- memcpy(wordPtr, pIndexMapSet->map[i].mapToNew, dataLen);
-
- /* advance pointer, maintaining 32-bit alignment */
- ptr = ((u1*) wordPtr) + dataLen;
- ptr = (u1*) (((int) ptr + 3) & ~3);
- }
-
- if (ptr - (u1*) pIndexMapSet->chunkData != chunkLen) {
- LOGE("GLITCH: expected len=%d, actual=%d\n",
- chunkLen, ptr - (u1*) pIndexMapSet->chunkData);
- dvmAbort();
- }
-
- return true;
-}
-
-/*
- * Construct an "expanding" chunk, with maps that convert instructions
- * with reduced constants back to their full original values.
- */
-#if DVM_RESOLVER_CACHE == DVM_RC_EXPANDING
-static bool constructExpandingDataChunk(IndexMapSet* pIndexMapSet)
-{
- int chunkLen = 0;
- int i;
-
- pIndexMapSet->chunkType = kDexChunkExpandingIndexMap;
-
- /*
- * Compute space requirements and allocate storage.
- */
- for (i = 0; i < kNumIndexMaps; i++) {
- /* space for the length word */
- chunkLen += sizeof(u4);
-
- /* add data length, round up to 32-bit boundary */
- chunkLen += pIndexMapSet->map[i].newCount * sizeof(u2);
- chunkLen = (chunkLen + 3) & ~3;
- }
-
- pIndexMapSet->chunkDataLen = chunkLen;
- pIndexMapSet->chunkData = (u1*) calloc(1, chunkLen);
- if (pIndexMapSet->chunkData == NULL)
- return false;
-
- /*
- * Copy the data in.
- */
- u1* ptr = pIndexMapSet->chunkData;
- for (i = 0; i < kNumIndexMaps; i++) {
- u4* wordPtr = (u4*) ptr;
- int dataLen = pIndexMapSet->map[i].newCount * sizeof(u2);
-
- *wordPtr++ = pIndexMapSet->map[i].newCount;
- if (dataLen != 0)
- memcpy(wordPtr, pIndexMapSet->map[i].mapToOld, dataLen);
-
- /* advance pointer, maintaining 32-bit alignment */
- ptr = ((u1*) wordPtr) + dataLen;
- ptr = (u1*) (((int) ptr + 3) & ~3);
- }
-
- if (ptr - (u1*) pIndexMapSet->chunkData != chunkLen) {
- LOGE("GLITCH: expected len=%d, actual=%d\n",
- chunkLen, ptr - (u1*) pIndexMapSet->chunkData);
- dvmAbort();
- }
-
- return true;
-}
-#endif
-
-/*
- * Construct the "chunk" of data that will be appended to the optimized DEX
- * file.
- */
-static bool constructDataChunk(IndexMapSet* pIndexMapSet)
-{
- assert(sizeof(pIndexMapSet->map[0].mapToOld[0]) == sizeof(u2));
- assert(sizeof(pIndexMapSet->map[0].mapToNew[0]) == sizeof(u2));
-
-#if DVM_RESOLVER_CACHE == DVM_RC_EXPANDING
- return constructExpandingDataChunk(pIndexMapSet);
-#else
- return constructReducingDataChunk(pIndexMapSet);
-#endif
-}
-
-/*
- * Allocate storage to hold the maps.
- */
-static IndexMapSet* createIndexMapSet(const DexFile* pDexFile,
- ScanResults* pResults)
-{
- IndexMapSet* pIndexMapSet;
- bool okay = true;
-
- pIndexMapSet = calloc(1, sizeof(*pIndexMapSet));
- if (pIndexMapSet == NULL)
- return NULL;
-
- okay = okay && constructIndexMap(pDexFile->pHeader->typeIdsSize,
- pResults->usedClasses, &pIndexMapSet->map[kMapClasses]);
- okay = okay && constructIndexMap(pDexFile->pHeader->methodIdsSize,
- pResults->usedMethods, &pIndexMapSet->map[kMapMethods]);
- okay = okay && constructIndexMap(pDexFile->pHeader->fieldIdsSize,
- pResults->usedFields, &pIndexMapSet->map[kMapFields]);
- okay = okay && constructIndexMap(pDexFile->pHeader->stringIdsSize,
- pResults->usedStrings, &pIndexMapSet->map[kMapStrings]);
-
- LOGVV("Constr: %d %d %d %d\n",
- pIndexMapSet->map[kMapClasses].mapToOld[0],
- pIndexMapSet->map[kMapMethods].mapToOld[0],
- pIndexMapSet->map[kMapFields].mapToOld[0],
- pIndexMapSet->map[kMapStrings].mapToOld[0]);
-
- okay = okay && constructDataChunk(pIndexMapSet);
-
- if (!okay) {
- dvmFreeIndexMapSet(pIndexMapSet);
- return NULL;
- }
-
- return pIndexMapSet;
-}
-
-/*
- * Free map storage.
- *
- * "pIndexMapSet" may be incomplete.
- */
-void dvmFreeIndexMapSet(IndexMapSet* pIndexMapSet)
-{
- int i;
-
- if (pIndexMapSet == NULL)
- return;
-
- for (i = 0; i < kNumIndexMaps; i++) {
- free(pIndexMapSet->map[i].mapToOld);
- free(pIndexMapSet->map[i].mapToNew);
- }
- free(pIndexMapSet->chunkData);
- free(pIndexMapSet);
-}
-
-/*
- * Rewrite constant indexes to reduce heap requirements.
- */
-IndexMapSet* dvmRewriteConstants(DvmDex* pDvmDex)
-{
-#if (DVM_RESOLVER_CACHE != DVM_RC_REDUCING) && \
- (DVM_RESOLVER_CACHE != DVM_RC_EXPANDING)
- /* nothing to do */
- return NULL;
-#endif
-
- /*
- * We're looking for instructions that use "constant pool" entries for
- * classes, methods, fields, and strings. Many field and method entries
- * are optimized away, and many string constants are never accessed from
- * code or annotations.
- */
- ScanResults* pResults = allocScanResults(pDvmDex->pDexFile);
- forAllMethods(pDvmDex->pDexFile, markUsedConstants, pResults);
-
- summarizeResults(pDvmDex, pResults);
-
- /*
- * Allocate and populate the index maps.
- */
- IndexMapSet* pIndexMapSet = createIndexMapSet(pDvmDex->pDexFile, pResults);
-#if DVM_RESOLVER_CACHE == DVM_RC_EXPANDING
- if (pIndexMapSet != NULL) {
- /*
- * Rewrite the constants to use the reduced set.
- */
- forAllMethods(pDvmDex->pDexFile, updateUsedConstants, pIndexMapSet);
- }
-#endif
-
- freeScanResults(pResults);
-
- return pIndexMapSet;
-}