summaryrefslogtreecommitdiff
path: root/vm/analysis/RegisterMap.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'vm/analysis/RegisterMap.cpp')
-rw-r--r--vm/analysis/RegisterMap.cpp1833
1 files changed, 1833 insertions, 0 deletions
diff --git a/vm/analysis/RegisterMap.cpp b/vm/analysis/RegisterMap.cpp
new file mode 100644
index 0000000..298887d
--- /dev/null
+++ b/vm/analysis/RegisterMap.cpp
@@ -0,0 +1,1833 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * This code generate "register maps" for Dalvik bytecode. In a stack-based
+ * VM we might call these "stack maps". They are used to increase the
+ * precision in the garbage collector when scanning references in the
+ * interpreter thread stacks.
+ */
+#include "Dalvik.h"
+#include "UniquePtr.h"
+#include "analysis/CodeVerify.h"
+#include "analysis/RegisterMap.h"
+#include "libdex/DexCatch.h"
+#include "libdex/InstrUtils.h"
+#include "libdex/Leb128.h"
+
+#include <stddef.h>
+
+/* double-check the compression */
+#define REGISTER_MAP_VERIFY false
+
+/* verbose logging */
+#define REGISTER_MAP_VERBOSE false
+
+//#define REGISTER_MAP_STATS
+
+// fwd
+static void outputTypeVector(const RegType* regs, int insnRegCount, u1* data);
+static bool verifyMap(VerifierData* vdata, const RegisterMap* pMap);
+static int compareMaps(const RegisterMap* pMap1, const RegisterMap* pMap2);
+
+#ifdef REGISTER_MAP_STATS
+static void computeMapStats(RegisterMap* pMap, const Method* method);
+#endif
+static RegisterMap* compressMapDifferential(const RegisterMap* pMap,\
+ const Method* meth);
+static RegisterMap* uncompressMapDifferential(const RegisterMap* pMap);
+
+#ifdef REGISTER_MAP_STATS
+/*
+ * Generate some statistics on the register maps we create and use.
+ */
+#define kMaxGcPointGap 50
+#define kUpdatePosnMinRegs 24
+#define kNumUpdatePosns 8
+#define kMaxDiffBits 20
+struct MapStats {
+ /*
+ * Buckets measuring the distance between GC points. This tells us how
+ * many bits we need to encode the advancing program counter. We ignore
+ * some of the "long tail" entries.
+ */
+ int gcPointGap[kMaxGcPointGap];
+
+ /*
+ * Number of gaps. Equal to (number of gcPoints - number of methods),
+ * since the computation isn't including the initial gap.
+ */
+ int gcGapCount;
+
+ /*
+ * Number of gaps.
+ */
+ int totalGcPointCount;
+
+ /*
+ * For larger methods (>= 24 registers), measure in which octant register
+ * updates occur. This should help us understand whether register
+ * changes tend to cluster in the low regs even for large methods.
+ */
+ int updatePosn[kNumUpdatePosns];
+
+ /*
+ * For all methods, count up the number of changes to registers < 16
+ * and >= 16.
+ */
+ int updateLT16;
+ int updateGE16;
+
+ /*
+ * Histogram of the number of bits that differ between adjacent entries.
+ */
+ int numDiffBits[kMaxDiffBits];
+
+
+ /*
+ * Track the number of expanded maps, and the heap space required to
+ * hold them.
+ */
+ int numExpandedMaps;
+ int totalExpandedMapSize;
+};
+#endif
+
+/*
+ * Prepare some things.
+ */
+bool dvmRegisterMapStartup()
+{
+#ifdef REGISTER_MAP_STATS
+ MapStats* pStats = calloc(1, sizeof(MapStats));
+ gDvm.registerMapStats = pStats;
+#endif
+ return true;
+}
+
+/*
+ * Clean up.
+ */
+void dvmRegisterMapShutdown()
+{
+#ifdef REGISTER_MAP_STATS
+ free(gDvm.registerMapStats);
+#endif
+}
+
+/*
+ * Write stats to log file.
+ */
+void dvmRegisterMapDumpStats()
+{
+#ifdef REGISTER_MAP_STATS
+ MapStats* pStats = (MapStats*) gDvm.registerMapStats;
+ int i, end;
+
+ for (end = kMaxGcPointGap-1; end >= 0; end--) {
+ if (pStats->gcPointGap[end] != 0)
+ break;
+ }
+
+ LOGI("Register Map gcPointGap stats (diff count=%d, total=%d):",
+ pStats->gcGapCount, pStats->totalGcPointCount);
+ assert(pStats->gcPointGap[0] == 0);
+ for (i = 1; i <= end; i++) {
+ LOGI(" %2d %d", i, pStats->gcPointGap[i]);
+ }
+
+
+ for (end = kMaxDiffBits-1; end >= 0; end--) {
+ if (pStats->numDiffBits[end] != 0)
+ break;
+ }
+
+ LOGI("Register Map bit difference stats:");
+ for (i = 0; i <= end; i++) {
+ LOGI(" %2d %d", i, pStats->numDiffBits[i]);
+ }
+
+
+ LOGI("Register Map update position stats (lt16=%d ge16=%d):",
+ pStats->updateLT16, pStats->updateGE16);
+ for (i = 0; i < kNumUpdatePosns; i++) {
+ LOGI(" %2d %d", i, pStats->updatePosn[i]);
+ }
+#endif
+}
+
+
+/*
+ * ===========================================================================
+ * Map generation
+ * ===========================================================================
+ */
+
+/*
+ * Generate the register map for a method that has just been verified
+ * (i.e. we're doing this as part of verification).
+ *
+ * For type-precise determination we have all the data we need, so we
+ * just need to encode it in some clever fashion.
+ *
+ * Returns a pointer to a newly-allocated RegisterMap, or NULL on failure.
+ */
+RegisterMap* dvmGenerateRegisterMapV(VerifierData* vdata)
+{
+ static const int kHeaderSize = offsetof(RegisterMap, data);
+ RegisterMap* pMap = NULL;
+ RegisterMap* pResult = NULL;
+ RegisterMapFormat format;
+ u1 regWidth;
+ u1* mapData;
+ int i, bytesForAddr, gcPointCount;
+ int bufSize;
+
+ if (vdata->method->registersSize >= 2048) {
+ LOGE("ERROR: register map can't handle %d registers",
+ vdata->method->registersSize);
+ goto bail;
+ }
+ regWidth = (vdata->method->registersSize + 7) / 8;
+
+ /*
+ * Decide if we need 8 or 16 bits to hold the address. Strictly speaking
+ * we only need 16 bits if we actually encode an address >= 256 -- if
+ * the method has a section at the end without GC points (e.g. array
+ * data) we don't need to count it. The situation is unusual, and
+ * detecting it requires scanning the entire method, so we don't bother.
+ */
+ if (vdata->insnsSize < 256) {
+ format = kRegMapFormatCompact8;
+ bytesForAddr = 1;
+ } else {
+ format = kRegMapFormatCompact16;
+ bytesForAddr = 2;
+ }
+
+ /*
+ * Count up the number of GC point instructions.
+ *
+ * NOTE: this does not automatically include the first instruction,
+ * since we don't count method entry as a GC point.
+ */
+ gcPointCount = 0;
+ for (i = 0; i < (int) vdata->insnsSize; i++) {
+ if (dvmInsnIsGcPoint(vdata->insnFlags, i))
+ gcPointCount++;
+ }
+ if (gcPointCount >= 65536) {
+ /* we could handle this, but in practice we don't get near this */
+ LOGE("ERROR: register map can't handle %d gc points in one method",
+ gcPointCount);
+ goto bail;
+ }
+
+ /*
+ * Allocate a buffer to hold the map data.
+ */
+ bufSize = kHeaderSize + gcPointCount * (bytesForAddr + regWidth);
+
+ LOGV("+++ grm: %s.%s (adr=%d gpc=%d rwd=%d bsz=%d)",
+ vdata->method->clazz->descriptor, vdata->method->name,
+ bytesForAddr, gcPointCount, regWidth, bufSize);
+
+ pMap = (RegisterMap*) malloc(bufSize);
+ dvmRegisterMapSetFormat(pMap, format);
+ dvmRegisterMapSetOnHeap(pMap, true);
+ dvmRegisterMapSetRegWidth(pMap, regWidth);
+ dvmRegisterMapSetNumEntries(pMap, gcPointCount);
+
+ /*
+ * Populate it.
+ */
+ mapData = pMap->data;
+ for (i = 0; i < (int) vdata->insnsSize; i++) {
+ if (dvmInsnIsGcPoint(vdata->insnFlags, i)) {
+ assert(vdata->registerLines[i].regTypes != NULL);
+ if (format == kRegMapFormatCompact8) {
+ *mapData++ = i;
+ } else /*kRegMapFormatCompact16*/ {
+ *mapData++ = i & 0xff;
+ *mapData++ = i >> 8;
+ }
+ outputTypeVector(vdata->registerLines[i].regTypes,
+ vdata->insnRegCount, mapData);
+ mapData += regWidth;
+ }
+ }
+
+ LOGV("mapData=%p pMap=%p bufSize=%d", mapData, pMap, bufSize);
+ assert(mapData - (const u1*) pMap == bufSize);
+
+ if (REGISTER_MAP_VERIFY && !verifyMap(vdata, pMap))
+ goto bail;
+#ifdef REGISTER_MAP_STATS
+ computeMapStats(pMap, vdata->method);
+#endif
+
+ /*
+ * Try to compress the map.
+ */
+ RegisterMap* pCompMap;
+
+ pCompMap = compressMapDifferential(pMap, vdata->method);
+ if (pCompMap != NULL) {
+ if (REGISTER_MAP_VERIFY) {
+ /*
+ * Expand the compressed map we just created, and compare it
+ * to the original. Abort the VM if it doesn't match up.
+ */
+ RegisterMap* pUncompMap;
+ pUncompMap = uncompressMapDifferential(pCompMap);
+ if (pUncompMap == NULL) {
+ LOGE("Map failed to uncompress - %s.%s",
+ vdata->method->clazz->descriptor,
+ vdata->method->name);
+ free(pCompMap);
+ /* bad - compression is broken or we're out of memory */
+ dvmAbort();
+ } else {
+ if (compareMaps(pMap, pUncompMap) != 0) {
+ LOGE("Map comparison failed - %s.%s",
+ vdata->method->clazz->descriptor,
+ vdata->method->name);
+ free(pCompMap);
+ /* bad - compression is broken */
+ dvmAbort();
+ }
+
+ /* verify succeeded */
+ delete pUncompMap;
+ }
+ }
+
+ if (REGISTER_MAP_VERBOSE) {
+ LOGD("Good compress on %s.%s",
+ vdata->method->clazz->descriptor,
+ vdata->method->name);
+ }
+ free(pMap);
+ pMap = pCompMap;
+ } else {
+ if (REGISTER_MAP_VERBOSE) {
+ LOGD("Unable to compress %s.%s (ent=%d rw=%d)",
+ vdata->method->clazz->descriptor,
+ vdata->method->name,
+ dvmRegisterMapGetNumEntries(pMap),
+ dvmRegisterMapGetRegWidth(pMap));
+ }
+ }
+
+ pResult = pMap;
+
+bail:
+ return pResult;
+}
+
+/*
+ * Release the storage held by a RegisterMap.
+ */
+void dvmFreeRegisterMap(RegisterMap* pMap)
+{
+ if (pMap == NULL)
+ return;
+
+ assert(dvmRegisterMapGetOnHeap(pMap));
+ free(pMap);
+}
+
+/*
+ * Determine if the RegType value is a reference type.
+ *
+ * Ordinarily we include kRegTypeZero in the "is it a reference"
+ * check. There's no value in doing so here, because we know
+ * the register can't hold anything but zero.
+ */
+static inline bool isReferenceType(RegType type)
+{
+ return (type > kRegTypeMAX || type == kRegTypeUninit);
+}
+
+/*
+ * Given a line of registers, output a bit vector that indicates whether
+ * or not the register holds a reference type (which could be null).
+ *
+ * We use '1' to indicate it's a reference, '0' for anything else (numeric
+ * value, uninitialized data, merge conflict). Register 0 will be found
+ * in the low bit of the first byte.
+ */
+static void outputTypeVector(const RegType* regs, int insnRegCount, u1* data)
+{
+ u1 val = 0;
+ int i;
+
+ for (i = 0; i < insnRegCount; i++) {
+ RegType type = *regs++;
+ val >>= 1;
+ if (isReferenceType(type))
+ val |= 0x80; /* set hi bit */
+
+ if ((i & 0x07) == 7)
+ *data++ = val;
+ }
+ if ((i & 0x07) != 0) {
+ /* flush bits from last byte */
+ val >>= 8 - (i & 0x07);
+ *data++ = val;
+ }
+}
+
+/*
+ * Print the map as a series of binary strings.
+ *
+ * Pass in method->registersSize if known, or -1 if not.
+ */
+static void dumpRegisterMap(const RegisterMap* pMap, int registersSize)
+{
+ const u1* rawMap = pMap->data;
+ const RegisterMapFormat format = dvmRegisterMapGetFormat(pMap);
+ const int numEntries = dvmRegisterMapGetNumEntries(pMap);
+ const int regWidth = dvmRegisterMapGetRegWidth(pMap);
+ int addrWidth;
+
+ switch (format) {
+ case kRegMapFormatCompact8:
+ addrWidth = 1;
+ break;
+ case kRegMapFormatCompact16:
+ addrWidth = 2;
+ break;
+ default:
+ /* can't happen */
+ LOGE("Can only dump Compact8 / Compact16 maps (not %d)", format);
+ return;
+ }
+
+ if (registersSize < 0)
+ registersSize = 8 * regWidth;
+ assert(registersSize <= regWidth * 8);
+
+ int ent;
+ for (ent = 0; ent < numEntries; ent++) {
+ int i, addr;
+
+ addr = *rawMap++;
+ if (addrWidth > 1)
+ addr |= (*rawMap++) << 8;
+
+ const u1* dataStart = rawMap;
+ u1 val = 0;
+
+ /* create binary string */
+ char outBuf[registersSize +1];
+ for (i = 0; i < registersSize; i++) {
+ val >>= 1;
+ if ((i & 0x07) == 0)
+ val = *rawMap++;
+
+ outBuf[i] = '0' + (val & 0x01);
+ }
+ outBuf[i] = '\0';
+
+ /* back up and create hex dump */
+ char hexBuf[regWidth * 3 +1];
+ char* cp = hexBuf;
+ rawMap = dataStart;
+ for (i = 0; i < regWidth; i++) {
+ sprintf(cp, " %02x", *rawMap++);
+ cp += 3;
+ }
+ hexBuf[i * 3] = '\0';
+
+ LOGD(" %04x %s %s", addr, outBuf, hexBuf);
+ }
+}
+
+/*
+ * Double-check the map.
+ *
+ * We run through all of the data in the map, and compare it to the original.
+ * Only works on uncompressed data.
+ */
+static bool verifyMap(VerifierData* vdata, const RegisterMap* pMap)
+{
+ const u1* rawMap = pMap->data;
+ const RegisterMapFormat format = dvmRegisterMapGetFormat(pMap);
+ const int numEntries = dvmRegisterMapGetNumEntries(pMap);
+ int ent;
+ bool dumpMap = false;
+
+ if (false) {
+ const char* cd = "Landroid/net/http/Request;";
+ const char* mn = "readResponse";
+ if (strcmp(vdata->method->clazz->descriptor, cd) == 0 &&
+ strcmp(vdata->method->name, mn) == 0)
+ {
+ char* desc;
+ desc = dexProtoCopyMethodDescriptor(&vdata->method->prototype);
+ LOGI("Map for %s.%s %s", vdata->method->clazz->descriptor,
+ vdata->method->name, desc);
+ free(desc);
+
+ dumpMap = true;
+ }
+ }
+
+ if ((vdata->method->registersSize + 7) / 8 != pMap->regWidth) {
+ LOGE("GLITCH: registersSize=%d, regWidth=%d",
+ vdata->method->registersSize, pMap->regWidth);
+ return false;
+ }
+
+ for (ent = 0; ent < numEntries; ent++) {
+ int addr;
+
+ switch (format) {
+ case kRegMapFormatCompact8:
+ addr = *rawMap++;
+ break;
+ case kRegMapFormatCompact16:
+ addr = *rawMap++;
+ addr |= (*rawMap++) << 8;
+ break;
+ default:
+ /* shouldn't happen */
+ LOGE("GLITCH: bad format (%d)", format);
+ dvmAbort();
+ }
+
+ const RegType* regs = vdata->registerLines[addr].regTypes;
+ if (regs == NULL) {
+ LOGE("GLITCH: addr %d has no data", addr);
+ return false;
+ }
+
+ u1 val = 0;
+ int i;
+
+ for (i = 0; i < vdata->method->registersSize; i++) {
+ bool bitIsRef, regIsRef;
+
+ val >>= 1;
+ if ((i & 0x07) == 0) {
+ /* load next byte of data */
+ val = *rawMap++;
+ }
+
+ bitIsRef = val & 0x01;
+
+ RegType type = regs[i];
+ regIsRef = isReferenceType(type);
+
+ if (bitIsRef != regIsRef) {
+ LOGE("GLITCH: addr %d reg %d: bit=%d reg=%d(%d)",
+ addr, i, bitIsRef, regIsRef, type);
+ return false;
+ }
+ }
+
+ /* rawMap now points to the address field of the next entry */
+ }
+
+ if (dumpMap)
+ dumpRegisterMap(pMap, vdata->method->registersSize);
+
+ return true;
+}
+
+
+/*
+ * ===========================================================================
+ * DEX generation & parsing
+ * ===========================================================================
+ */
+
+/*
+ * Advance "ptr" to ensure 32-bit alignment.
+ */
+static inline u1* align32(u1* ptr)
+{
+ return (u1*) (((int) ptr + 3) & ~0x03);
+}
+
+/*
+ * Compute the size, in bytes, of a register map.
+ */
+static size_t computeRegisterMapSize(const RegisterMap* pMap)
+{
+ static const int kHeaderSize = offsetof(RegisterMap, data);
+ u1 format = dvmRegisterMapGetFormat(pMap);
+ u2 numEntries = dvmRegisterMapGetNumEntries(pMap);
+
+ assert(pMap != NULL);
+
+ switch (format) {
+ case kRegMapFormatNone:
+ return 1;
+ case kRegMapFormatCompact8:
+ return kHeaderSize + (1 + pMap->regWidth) * numEntries;
+ case kRegMapFormatCompact16:
+ return kHeaderSize + (2 + pMap->regWidth) * numEntries;
+ case kRegMapFormatDifferential:
+ {
+ /* kHeaderSize + decoded ULEB128 length */
+ const u1* ptr = pMap->data;
+ int len = readUnsignedLeb128(&ptr);
+ return len + (ptr - (u1*) pMap);
+ }
+ default:
+ LOGE("Bad register map format %d", format);
+ dvmAbort();
+ return 0;
+ }
+}
+
+/*
+ * Output the map for a single method, if it has one.
+ *
+ * Abstract and native methods have no map. All others are expected to
+ * have one, since we know the class verified successfully.
+ *
+ * This strips the "allocated on heap" flag from the format byte, so that
+ * direct-mapped maps are correctly identified as such.
+ */
+static bool writeMapForMethod(const Method* meth, u1** pPtr)
+{
+ if (meth->registerMap == NULL) {
+ if (!dvmIsAbstractMethod(meth) && !dvmIsNativeMethod(meth)) {
+ LOGW("Warning: no map available for %s.%s",
+ meth->clazz->descriptor, meth->name);
+ /* weird, but keep going */
+ }
+ *(*pPtr)++ = kRegMapFormatNone;
+ return true;
+ }
+
+ /* serialize map into the buffer */
+ size_t mapSize = computeRegisterMapSize(meth->registerMap);
+ memcpy(*pPtr, meth->registerMap, mapSize);
+
+ /* strip the "on heap" flag out of the format byte, which is always first */
+ assert(**pPtr == meth->registerMap->format);
+ **pPtr &= ~(kRegMapFormatOnHeap);
+
+ *pPtr += mapSize;
+
+ return true;
+}
+
+/*
+ * Write maps for all methods in the specified class to the buffer, which
+ * can hold at most "length" bytes. "*pPtr" will be advanced past the end
+ * of the data we write.
+ */
+static bool writeMapsAllMethods(DvmDex* pDvmDex, const ClassObject* clazz,
+ u1** pPtr, size_t length)
+{
+ RegisterMapMethodPool* pMethodPool;
+ u1* ptr = *pPtr;
+ int i, methodCount;
+
+ /* artificial limit */
+ if (clazz->virtualMethodCount + clazz->directMethodCount >= 65536) {
+ LOGE("Too many methods in %s", clazz->descriptor);
+ return false;
+ }
+
+ pMethodPool = (RegisterMapMethodPool*) ptr;
+ ptr += offsetof(RegisterMapMethodPool, methodData);
+ methodCount = 0;
+
+ /*
+ * Run through all methods, direct then virtual. The class loader will
+ * traverse them in the same order. (We could split them into two
+ * distinct pieces, but there doesn't appear to be any value in doing
+ * so other than that it makes class loading slightly less fragile.)
+ *
+ * The class loader won't know about miranda methods at the point
+ * where it parses this, so we omit those.
+ *
+ * TODO: consider omitting all native/abstract definitions. Should be
+ * safe, though we lose the ability to sanity-check against the
+ * method counts in the DEX file.
+ */
+ for (i = 0; i < clazz->directMethodCount; i++) {
+ const Method* meth = &clazz->directMethods[i];
+ if (dvmIsMirandaMethod(meth))
+ continue;
+ if (!writeMapForMethod(&clazz->directMethods[i], &ptr)) {
+ return false;
+ }
+ methodCount++;
+ //ptr = align32(ptr);
+ }
+
+ for (i = 0; i < clazz->virtualMethodCount; i++) {
+ const Method* meth = &clazz->virtualMethods[i];
+ if (dvmIsMirandaMethod(meth))
+ continue;
+ if (!writeMapForMethod(&clazz->virtualMethods[i], &ptr)) {
+ return false;
+ }
+ methodCount++;
+ //ptr = align32(ptr);
+ }
+
+ pMethodPool->methodCount = methodCount;
+
+ *pPtr = ptr;
+ return true;
+}
+
+/*
+ * Write maps for all classes to the specified buffer, which can hold at
+ * most "length" bytes.
+ *
+ * Returns the actual length used, or 0 on failure.
+ */
+static size_t writeMapsAllClasses(DvmDex* pDvmDex, u1* basePtr, size_t length)
+{
+ DexFile* pDexFile = pDvmDex->pDexFile;
+ u4 count = pDexFile->pHeader->classDefsSize;
+ RegisterMapClassPool* pClassPool;
+ u4* offsetTable;
+ u1* ptr = basePtr;
+ u4 idx;
+
+ assert(gDvm.optimizing);
+
+ pClassPool = (RegisterMapClassPool*) ptr;
+ ptr += offsetof(RegisterMapClassPool, classDataOffset);
+ offsetTable = (u4*) ptr;
+ ptr += count * sizeof(u4);
+
+ pClassPool->numClasses = count;
+
+ /*
+ * We want an entry for every class, loaded or not.
+ */
+ for (idx = 0; idx < count; idx++) {
+ const DexClassDef* pClassDef;
+ const char* classDescriptor;
+ ClassObject* clazz;
+
+ pClassDef = dexGetClassDef(pDexFile, idx);
+ classDescriptor = dexStringByTypeIdx(pDexFile, pClassDef->classIdx);
+
+ /*
+ * All classes have been loaded into the bootstrap class loader.
+ * If we can find it, and it was successfully pre-verified, we
+ * run through its methods and add the register maps.
+ *
+ * If it wasn't pre-verified then we know it can't have any
+ * register maps. Classes that can't be loaded or failed
+ * verification get an empty slot in the index.
+ */
+ clazz = NULL;
+ if ((pClassDef->accessFlags & CLASS_ISPREVERIFIED) != 0)
+ clazz = dvmLookupClass(classDescriptor, NULL, false);
+
+ if (clazz != NULL) {
+ offsetTable[idx] = ptr - basePtr;
+ LOGVV("%d -> offset %d (%p-%p)",
+ idx, offsetTable[idx], ptr, basePtr);
+
+ if (!writeMapsAllMethods(pDvmDex, clazz, &ptr,
+ length - (ptr - basePtr)))
+ {
+ return 0;
+ }
+
+ ptr = align32(ptr);
+ LOGVV("Size %s (%d+%d methods): %d", clazz->descriptor,
+ clazz->directMethodCount, clazz->virtualMethodCount,
+ (ptr - basePtr) - offsetTable[idx]);
+ } else {
+ LOGV("%4d NOT mapadding '%s'", idx, classDescriptor);
+ assert(offsetTable[idx] == 0);
+ }
+ }
+
+ if (ptr - basePtr >= (int)length) {
+ /* a bit late */
+ LOGE("Buffer overrun");
+ dvmAbort();
+ }
+
+ return ptr - basePtr;
+}
+
+/*
+ * Generate a register map set for all verified classes in "pDvmDex".
+ */
+RegisterMapBuilder* dvmGenerateRegisterMaps(DvmDex* pDvmDex)
+{
+ RegisterMapBuilder* pBuilder;
+
+ pBuilder = (RegisterMapBuilder*) calloc(1, sizeof(RegisterMapBuilder));
+ if (pBuilder == NULL)
+ return NULL;
+
+ /*
+ * We have a couple of options here:
+ * (1) Compute the size of the output, and malloc a buffer.
+ * (2) Create a "large-enough" anonymous mmap region.
+ *
+ * The nice thing about option #2 is that we don't have to traverse
+ * all of the classes and methods twice. The risk is that we might
+ * not make the region large enough. Since the pages aren't mapped
+ * until used we can allocate a semi-absurd amount of memory without
+ * worrying about the effect on the rest of the system.
+ *
+ * The basic encoding on the largest jar file requires about 1MB of
+ * storage. We map out 4MB here. (TODO: guarantee that the last
+ * page of the mapping is marked invalid, so we reliably fail if
+ * we overrun.)
+ */
+ if (sysCreatePrivateMap(4 * 1024 * 1024, &pBuilder->memMap) != 0) {
+ free(pBuilder);
+ return NULL;
+ }
+
+ /*
+ * Create the maps.
+ */
+ size_t actual = writeMapsAllClasses(pDvmDex, (u1*)pBuilder->memMap.addr,
+ pBuilder->memMap.length);
+ if (actual == 0) {
+ dvmFreeRegisterMapBuilder(pBuilder);
+ return NULL;
+ }
+
+ LOGV("TOTAL size of register maps: %d", actual);
+
+ pBuilder->data = pBuilder->memMap.addr;
+ pBuilder->size = actual;
+ return pBuilder;
+}
+
+/*
+ * Free the builder.
+ */
+void dvmFreeRegisterMapBuilder(RegisterMapBuilder* pBuilder)
+{
+ if (pBuilder == NULL)
+ return;
+
+ sysReleaseShmem(&pBuilder->memMap);
+ free(pBuilder);
+}
+
+
+/*
+ * Find the data for the specified class.
+ *
+ * If there's no register map data, or none for this class, we return NULL.
+ */
+const void* dvmRegisterMapGetClassData(const DexFile* pDexFile, u4 classIdx,
+ u4* pNumMaps)
+{
+ const RegisterMapClassPool* pClassPool;
+ const RegisterMapMethodPool* pMethodPool;
+
+ pClassPool = (const RegisterMapClassPool*) pDexFile->pRegisterMapPool;
+ if (pClassPool == NULL)
+ return NULL;
+
+ if (classIdx >= pClassPool->numClasses) {
+ LOGE("bad class index (%d vs %d)", classIdx, pClassPool->numClasses);
+ dvmAbort();
+ }
+
+ u4 classOffset = pClassPool->classDataOffset[classIdx];
+ if (classOffset == 0) {
+ LOGV("+++ no map for classIdx=%d", classIdx);
+ return NULL;
+ }
+
+ pMethodPool =
+ (const RegisterMapMethodPool*) (((u1*) pClassPool) + classOffset);
+ if (pNumMaps != NULL)
+ *pNumMaps = pMethodPool->methodCount;
+ return pMethodPool->methodData;
+}
+
+/*
+ * This advances "*pPtr" and returns its original value.
+ */
+const RegisterMap* dvmRegisterMapGetNext(const void** pPtr)
+{
+ const RegisterMap* pMap = (const RegisterMap*) *pPtr;
+
+ *pPtr = /*align32*/(((u1*) pMap) + computeRegisterMapSize(pMap));
+ LOGVV("getNext: %p -> %p (f=%#x w=%d e=%d)",
+ pMap, *pPtr, pMap->format, pMap->regWidth,
+ dvmRegisterMapGetNumEntries(pMap));
+ return pMap;
+}
+
+
+/*
+ * ===========================================================================
+ * Utility functions
+ * ===========================================================================
+ */
+
+/*
+ * Return the data for the specified address, or NULL if not found.
+ *
+ * The result must be released with dvmReleaseRegisterMapLine().
+ */
+const u1* dvmRegisterMapGetLine(const RegisterMap* pMap, int addr)
+{
+ int addrWidth, lineWidth;
+ u1 format = dvmRegisterMapGetFormat(pMap);
+ u2 numEntries = dvmRegisterMapGetNumEntries(pMap);
+
+ assert(numEntries > 0);
+
+ switch (format) {
+ case kRegMapFormatNone:
+ return NULL;
+ case kRegMapFormatCompact8:
+ addrWidth = 1;
+ break;
+ case kRegMapFormatCompact16:
+ addrWidth = 2;
+ break;
+ default:
+ LOGE("Unknown format %d", format);
+ dvmAbort();
+ return NULL;
+ }
+
+ lineWidth = addrWidth + pMap->regWidth;
+
+ /*
+ * Find the appropriate entry. Many maps are very small, some are very
+ * large.
+ */
+ static const int kSearchThreshold = 8;
+ const u1* data = NULL;
+ int lineAddr;
+
+ if (numEntries < kSearchThreshold) {
+ int i;
+ data = pMap->data;
+ for (i = numEntries; i > 0; i--) {
+ lineAddr = data[0];
+ if (addrWidth > 1)
+ lineAddr |= data[1] << 8;
+ if (lineAddr == addr)
+ return data + addrWidth;
+
+ data += lineWidth;
+ }
+ assert(data == pMap->data + lineWidth * numEntries);
+ } else {
+ int hi, lo, mid;
+
+ lo = 0;
+ hi = numEntries -1;
+
+ while (hi >= lo) {
+ mid = (hi + lo) / 2;
+ data = pMap->data + lineWidth * mid;
+
+ lineAddr = data[0];
+ if (addrWidth > 1)
+ lineAddr |= data[1] << 8;
+
+ if (addr > lineAddr) {
+ lo = mid + 1;
+ } else if (addr < lineAddr) {
+ hi = mid - 1;
+ } else {
+ return data + addrWidth;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Compare two register maps.
+ *
+ * Returns 0 if they're equal, nonzero if not.
+ */
+static int compareMaps(const RegisterMap* pMap1, const RegisterMap* pMap2)
+{
+ size_t size1, size2;
+
+ size1 = computeRegisterMapSize(pMap1);
+ size2 = computeRegisterMapSize(pMap2);
+ if (size1 != size2) {
+ LOGI("compareMaps: size mismatch (%zd vs %zd)", size1, size2);
+ return -1;
+ }
+
+ if (memcmp(pMap1, pMap2, size1) != 0) {
+ LOGI("compareMaps: content mismatch");
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Get the expanded form of the register map associated with the method.
+ *
+ * If the map is already in one of the uncompressed formats, we return
+ * immediately. Otherwise, we expand the map and replace method's register
+ * map pointer, freeing it if it was allocated on the heap.
+ *
+ * NOTE: this function is not synchronized; external locking is mandatory
+ * (unless we're in the zygote, where single-threaded access is guaranteed).
+ */
+const RegisterMap* dvmGetExpandedRegisterMap0(Method* method)
+{
+ const RegisterMap* curMap = method->registerMap;
+ RegisterMap* newMap;
+
+ if (curMap == NULL)
+ return NULL;
+
+ /* sanity check to ensure this isn't called w/o external locking */
+ /* (if we use this at a time other than during GC, fix/remove this test) */
+ if (true) {
+ if (!gDvm.zygote && dvmTryLockMutex(&gDvm.gcHeapLock) == 0) {
+ LOGE("GLITCH: dvmGetExpandedRegisterMap not called at GC time");
+ dvmAbort();
+ }
+ }
+
+ RegisterMapFormat format = dvmRegisterMapGetFormat(curMap);
+ switch (format) {
+ case kRegMapFormatCompact8:
+ case kRegMapFormatCompact16:
+ if (REGISTER_MAP_VERBOSE) {
+ if (dvmRegisterMapGetOnHeap(curMap)) {
+ LOGD("RegMap: already expanded: %s.%s",
+ method->clazz->descriptor, method->name);
+ } else {
+ LOGD("RegMap: stored w/o compression: %s.%s",
+ method->clazz->descriptor, method->name);
+ }
+ }
+ return curMap;
+ case kRegMapFormatDifferential:
+ newMap = uncompressMapDifferential(curMap);
+ break;
+ default:
+ LOGE("Unknown format %d in dvmGetExpandedRegisterMap", format);
+ dvmAbort();
+ newMap = NULL; // make gcc happy
+ }
+
+ if (newMap == NULL) {
+ LOGE("Map failed to uncompress (fmt=%d) %s.%s",
+ format, method->clazz->descriptor, method->name);
+ return NULL;
+ }
+
+#ifdef REGISTER_MAP_STATS
+ /*
+ * Gather and display some stats.
+ */
+ {
+ MapStats* pStats = (MapStats*) gDvm.registerMapStats;
+ pStats->numExpandedMaps++;
+ pStats->totalExpandedMapSize += computeRegisterMapSize(newMap);
+ LOGD("RMAP: count=%d size=%d",
+ pStats->numExpandedMaps, pStats->totalExpandedMapSize);
+ }
+#endif
+
+ IF_LOGV() {
+ char* desc = dexProtoCopyMethodDescriptor(&method->prototype);
+ LOGV("Expanding map -> %s.%s:%s",
+ method->clazz->descriptor, method->name, desc);
+ free(desc);
+ }
+
+ /*
+ * Update method, and free compressed map if it was sitting on the heap.
+ */
+ dvmSetRegisterMap(method, newMap);
+
+ if (dvmRegisterMapGetOnHeap(curMap))
+ dvmFreeRegisterMap((RegisterMap*) curMap);
+
+ return newMap;
+}
+
+
+/*
+ * ===========================================================================
+ * Map compression
+ * ===========================================================================
+ */
+
+/*
+Notes on map compression
+
+The idea is to create a compressed form that will be uncompressed before
+use, with the output possibly saved in a cache. This means we can use an
+approach that is unsuited for random access if we choose.
+
+In the event that a map simply does not work with our compression scheme,
+it's reasonable to store the map without compression. In the future we
+may want to have more than one compression scheme, and try each in turn,
+retaining the best. (We certainly want to keep the uncompressed form if it
+turns out to be smaller or even slightly larger than the compressed form.)
+
+Each entry consists of an address and a bit vector. Adjacent entries are
+strongly correlated, suggesting differential encoding.
+
+
+Ideally we would avoid outputting adjacent entries with identical
+bit vectors. However, the register values at a given address do not
+imply anything about the set of valid registers at subsequent addresses.
+We therefore cannot omit an entry.
+
+ If the thread stack has a PC at an address without a corresponding
+ entry in the register map, we must conservatively scan the registers in
+ that thread. This can happen when single-stepping in the debugger,
+ because the debugger is allowed to invoke arbitrary methods when
+ a thread is stopped at a breakpoint. If we can guarantee that a GC
+ thread scan will never happen while the debugger has that thread stopped,
+ then we can lift this restriction and simply omit entries that don't
+ change the bit vector from its previous state.
+
+Each entry advances the address value by at least 1 (measured in 16-bit
+"code units"). Looking at the bootclasspath entries, advancing by 2 units
+is most common. Advances by 1 unit are far less common than advances by
+2 units, but more common than 5, and things fall off rapidly. Gaps of
+up to 220 code units appear in some computationally intensive bits of code,
+but are exceedingly rare.
+
+If we sum up the number of transitions in a couple of ranges in framework.jar:
+ [1,4]: 188998 of 218922 gaps (86.3%)
+ [1,7]: 211647 of 218922 gaps (96.7%)
+Using a 3-bit delta, with one value reserved as an escape code, should
+yield good results for the address.
+
+These results would change dramatically if we reduced the set of GC
+points by e.g. removing instructions like integer divide that are only
+present because they can throw and cause an allocation.
+
+We also need to include an "initial gap", because the first few instructions
+in a method may not be GC points.
+
+
+By observation, many entries simply repeat the previous bit vector, or
+change only one or two bits. (This is with type-precise information;
+the rate of change of bits will be different if live-precise information
+is factored in).
+
+Looking again at adjacent entries in framework.jar:
+ 0 bits changed: 63.0%
+ 1 bit changed: 32.2%
+After that it falls off rapidly, e.g. the number of entries with 2 bits
+changed is usually less than 1/10th of the number of entries with 1 bit
+changed. A solution that allows us to encode 0- or 1- bit changes
+efficiently will do well.
+
+We still need to handle cases where a large number of bits change. We
+probably want a way to drop in a full copy of the bit vector when it's
+smaller than the representation of multiple bit changes.
+
+
+The bit-change information can be encoded as an index that tells the
+decoder to toggle the state. We want to encode the index in as few bits
+as possible, but we need to allow for fairly wide vectors (e.g. we have a
+method with 175 registers). We can deal with this in a couple of ways:
+(1) use an encoding that assumes few registers and has an escape code
+for larger numbers of registers; or (2) use different encodings based
+on how many total registers the method has. The choice depends to some
+extent on whether methods with large numbers of registers tend to modify
+the first 16 regs more often than the others.
+
+The last N registers hold method arguments. If the bytecode is expected
+to be examined in a debugger, "dx" ensures that the contents of these
+registers won't change. Depending upon the encoding format, we may be
+able to take advantage of this. We still have to encode the initial
+state, but we know we'll never have to output a bit change for the last
+N registers.
+
+Considering only methods with 16 or more registers, the "target octant"
+for register changes looks like this:
+ [ 43.1%, 16.4%, 6.5%, 6.2%, 7.4%, 8.8%, 9.7%, 1.8% ]
+As expected, there are fewer changes at the end of the list where the
+arguments are kept, and more changes at the start of the list because
+register values smaller than 16 can be used in compact Dalvik instructions
+and hence are favored for frequently-used values. In general, the first
+octant is considerably more active than later entries, the last octant
+is much less active, and the rest are all about the same.
+
+Looking at all bit changes in all methods, 94% are to registers 0-15. The
+encoding will benefit greatly by favoring the low-numbered registers.
+
+
+Some of the smaller methods have identical maps, and space could be
+saved by simply including a pointer to an earlier definition. This would
+be best accomplished by specifying a "pointer" format value, followed by
+a 3-byte (or ULEB128) offset. Implementing this would probably involve
+generating a hash value for each register map and maintaining a hash table.
+
+In some cases there are repeating patterns in the bit vector that aren't
+adjacent. These could benefit from a dictionary encoding. This doesn't
+really become useful until the methods reach a certain size though,
+and managing the dictionary may incur more overhead than we want.
+
+Large maps can be compressed significantly. The trouble is that, when
+we need to use them, we have to uncompress them onto the heap. We may
+get a better trade-off between storage size and heap usage by refusing to
+compress large maps, so that they can be memory mapped and used directly.
+(OTOH, only about 2% of the maps will ever actually be used.)
+
+
+----- differential format -----
+
+// common header
++00 1B format
++01 1B regWidth
++02 2B numEntries (little-endian)
++04 nB length in bytes of the data that follows, in ULEB128 format
+ (not strictly necessary; allows determination of size w/o full parse)
++05+ 1B initial address (0-127), high bit set if max addr >= 256
++06+ nB initial value for bit vector
+
+// for each entry
++00: CCCCBAAA
+
+ AAA: address difference. Values from 0 to 6 indicate an increment of 1
+ to 7. A value of 7 indicates that the address difference is large,
+ and the next byte is a ULEB128-encoded difference value.
+
+ B: determines the meaning of CCCC.
+
+ CCCC: if B is 0, this is the number of the bit to toggle (0-15).
+ If B is 1, this is a count of the number of changed bits (1-14). A value
+ of 0 means that no bits were changed, and a value of 15 indicates
+ that enough bits were changed that it required less space to output
+ the entire bit vector.
+
++01: (optional) ULEB128-encoded address difference
+
++01+: (optional) one or more ULEB128-encoded bit numbers, OR the entire
+ bit vector.
+
+The most common situation is an entry whose address has changed by 2-4
+code units, has no changes or just a single bit change, and the changed
+register is less than 16. We should therefore be able to encode a large
+number of entries with a single byte, which is half the size of the
+Compact8 encoding method.
+*/
+
+/*
+ * Compute some stats on an uncompressed register map.
+ */
+#ifdef REGISTER_MAP_STATS
+static void computeMapStats(RegisterMap* pMap, const Method* method)
+{
+ MapStats* pStats = (MapStats*) gDvm.registerMapStats;
+ const u1 format = dvmRegisterMapGetFormat(pMap);
+ const u2 numEntries = dvmRegisterMapGetNumEntries(pMap);
+ const u1* rawMap = pMap->data;
+ const u1* prevData = NULL;
+ int ent, addr, prevAddr = -1;
+
+ for (ent = 0; ent < numEntries; ent++) {
+ switch (format) {
+ case kRegMapFormatCompact8:
+ addr = *rawMap++;
+ break;
+ case kRegMapFormatCompact16:
+ addr = *rawMap++;
+ addr |= (*rawMap++) << 8;
+ break;
+ default:
+ /* shouldn't happen */
+ LOGE("GLITCH: bad format (%d)", format);
+ dvmAbort();
+ }
+
+ const u1* dataStart = rawMap;
+
+ pStats->totalGcPointCount++;
+
+ /*
+ * Gather "gap size" stats, i.e. the difference in addresses between
+ * successive GC points.
+ */
+ if (prevData != NULL) {
+ assert(prevAddr >= 0);
+ int addrDiff = addr - prevAddr;
+
+ if (addrDiff < 0) {
+ LOGE("GLITCH: address went backward (0x%04x->0x%04x, %s.%s)",
+ prevAddr, addr, method->clazz->descriptor, method->name);
+ } else if (addrDiff > kMaxGcPointGap) {
+ if (REGISTER_MAP_VERBOSE) {
+ LOGI("HEY: addrDiff is %d, max %d (0x%04x->0x%04x %s.%s)",
+ addrDiff, kMaxGcPointGap, prevAddr, addr,
+ method->clazz->descriptor, method->name);
+ }
+ /* skip this one */
+ } else {
+ pStats->gcPointGap[addrDiff]++;
+ }
+ pStats->gcGapCount++;
+
+
+ /*
+ * Compare bit vectors in adjacent entries. We want to count
+ * up the number of bits that differ (to see if we frequently
+ * change 0 or 1 bits) and get a sense for which part of the
+ * vector changes the most often (near the start, middle, end).
+ *
+ * We only do the vector position quantization if we have at
+ * least 16 registers in the method.
+ */
+ int numDiff = 0;
+ float div = (float) kNumUpdatePosns / method->registersSize;
+ int regByte;
+ for (regByte = 0; regByte < pMap->regWidth; regByte++) {
+ int prev, cur, bit;
+
+ prev = prevData[regByte];
+ cur = dataStart[regByte];
+
+ for (bit = 0; bit < 8; bit++) {
+ if (((prev >> bit) & 1) != ((cur >> bit) & 1)) {
+ numDiff++;
+
+ int bitNum = regByte * 8 + bit;
+
+ if (bitNum < 16)
+ pStats->updateLT16++;
+ else
+ pStats->updateGE16++;
+
+ if (method->registersSize < 16)
+ continue;
+
+ if (bitNum >= method->registersSize) {
+ /* stuff off the end should be zero in both */
+ LOGE("WEIRD: bit=%d (%d/%d), prev=%02x cur=%02x",
+ bit, regByte, method->registersSize,
+ prev, cur);
+ assert(false);
+ }
+ int idx = (int) (bitNum * div);
+ if (!(idx >= 0 && idx < kNumUpdatePosns)) {
+ LOGE("FAIL: bitNum=%d (of %d) div=%.3f idx=%d",
+ bitNum, method->registersSize, div, idx);
+ assert(false);
+ }
+ pStats->updatePosn[idx]++;
+ }
+ }
+ }
+
+ if (numDiff > kMaxDiffBits) {
+ if (REGISTER_MAP_VERBOSE) {
+ LOGI("WOW: numDiff is %d, max %d", numDiff, kMaxDiffBits);
+ }
+ } else {
+ pStats->numDiffBits[numDiff]++;
+ }
+ }
+
+ /* advance to start of next line */
+ rawMap += pMap->regWidth;
+
+ prevAddr = addr;
+ prevData = dataStart;
+ }
+}
+#endif
+
+/*
+ * Compute the difference between two bit vectors.
+ *
+ * If "lebOutBuf" is non-NULL, we output the bit indices in ULEB128 format
+ * as we go. Otherwise, we just generate the various counts.
+ *
+ * The bit vectors are compared byte-by-byte, so any unused bits at the
+ * end must be zero.
+ *
+ * Returns the number of bytes required to hold the ULEB128 output.
+ *
+ * If "pFirstBitChanged" or "pNumBitsChanged" are non-NULL, they will
+ * receive the index of the first changed bit and the number of changed
+ * bits, respectively.
+ */
+static int computeBitDiff(const u1* bits1, const u1* bits2, int byteWidth,
+ int* pFirstBitChanged, int* pNumBitsChanged, u1* lebOutBuf)
+{
+ int numBitsChanged = 0;
+ int firstBitChanged = -1;
+ int lebSize = 0;
+ int byteNum;
+
+ /*
+ * Run through the vectors, first comparing them at the byte level. This
+ * will yield a fairly quick result if nothing has changed between them.
+ */
+ for (byteNum = 0; byteNum < byteWidth; byteNum++) {
+ u1 byte1 = *bits1++;
+ u1 byte2 = *bits2++;
+ if (byte1 != byte2) {
+ /*
+ * Walk through the byte, identifying the changed bits.
+ */
+ int bitNum;
+ for (bitNum = 0; bitNum < 8; bitNum++) {
+ if (((byte1 >> bitNum) & 0x01) != ((byte2 >> bitNum) & 0x01)) {
+ int bitOffset = (byteNum << 3) + bitNum;
+
+ if (firstBitChanged < 0)
+ firstBitChanged = bitOffset;
+ numBitsChanged++;
+
+ if (lebOutBuf == NULL) {
+ lebSize += unsignedLeb128Size(bitOffset);
+ } else {
+ u1* curBuf = lebOutBuf;
+ lebOutBuf = writeUnsignedLeb128(lebOutBuf, bitOffset);
+ lebSize += lebOutBuf - curBuf;
+ }
+ }
+ }
+ }
+ }
+
+ if (numBitsChanged > 0)
+ assert(firstBitChanged >= 0);
+
+ if (pFirstBitChanged != NULL)
+ *pFirstBitChanged = firstBitChanged;
+ if (pNumBitsChanged != NULL)
+ *pNumBitsChanged = numBitsChanged;
+
+ return lebSize;
+}
+
+/*
+ * Compress the register map with differential encoding.
+ *
+ * "meth" is only needed for debug output.
+ *
+ * On success, returns a newly-allocated RegisterMap. If the map is not
+ * compatible for some reason, or fails to get smaller, this will return NULL.
+ */
+static RegisterMap* compressMapDifferential(const RegisterMap* pMap,
+ const Method* meth)
+{
+ RegisterMap* pNewMap = NULL;
+ int origSize = computeRegisterMapSize(pMap);
+ u1* tmpPtr;
+ int addrWidth, regWidth, numEntries;
+ bool debug = false;
+
+ if (false &&
+ strcmp(meth->clazz->descriptor, "Landroid/text/StaticLayout;") == 0 &&
+ strcmp(meth->name, "generate") == 0)
+ {
+ debug = true;
+ }
+
+ u1 format = dvmRegisterMapGetFormat(pMap);
+ switch (format) {
+ case kRegMapFormatCompact8:
+ addrWidth = 1;
+ break;
+ case kRegMapFormatCompact16:
+ addrWidth = 2;
+ break;
+ default:
+ LOGE("ERROR: can't compress map with format=%d", format);
+ return NULL;
+ }
+
+ regWidth = dvmRegisterMapGetRegWidth(pMap);
+ numEntries = dvmRegisterMapGetNumEntries(pMap);
+
+ if (debug) {
+ LOGI("COMPRESS: %s.%s aw=%d rw=%d ne=%d",
+ meth->clazz->descriptor, meth->name,
+ addrWidth, regWidth, numEntries);
+ dumpRegisterMap(pMap, -1);
+ }
+
+ if (numEntries <= 1) {
+ LOGV("Can't compress map with 0 or 1 entries");
+ return NULL;
+ }
+
+ /*
+ * We don't know how large the compressed data will be. It's possible
+ * for it to expand and become larger than the original. The header
+ * itself is variable-sized, so we generate everything into a temporary
+ * buffer and then copy it to form-fitting storage once we know how big
+ * it will be (and that it's smaller than the original).
+ *
+ * If we use a size that is equal to the size of the input map plus
+ * a value longer than a single entry can possibly expand to, we need
+ * only check for overflow at the end of each entry. The worst case
+ * for a single line is (1 + <ULEB8 address> + <full copy of vector>).
+ * Addresses are 16 bits, so that's (1 + 3 + regWidth).
+ *
+ * The initial address offset and bit vector will take up less than
+ * or equal to the amount of space required when uncompressed -- large
+ * initial offsets are rejected.
+ */
+ UniquePtr<u1[]> tmpBuf(new u1[origSize + (1 + 3 + regWidth)]);
+ if (tmpBuf.get() == NULL)
+ return NULL;
+
+ tmpPtr = tmpBuf.get();
+
+ const u1* mapData = pMap->data;
+ const u1* prevBits;
+ u2 addr, prevAddr;
+
+ addr = *mapData++;
+ if (addrWidth > 1)
+ addr |= (*mapData++) << 8;
+
+ if (addr >= 128) {
+ LOGV("Can't compress map with starting address >= 128");
+ return NULL;
+ }
+
+ /*
+ * Start by writing the initial address and bit vector data. The high
+ * bit of the initial address is used to indicate the required address
+ * width (which the decoder can't otherwise determine without parsing
+ * the compressed data).
+ */
+ *tmpPtr++ = addr | (addrWidth > 1 ? 0x80 : 0x00);
+ memcpy(tmpPtr, mapData, regWidth);
+
+ prevBits = mapData;
+ prevAddr = addr;
+
+ tmpPtr += regWidth;
+ mapData += regWidth;
+
+ /*
+ * Loop over all following entries.
+ */
+ for (int entry = 1; entry < numEntries; entry++) {
+ int addrDiff;
+ u1 key;
+
+ /*
+ * Pull out the address and figure out how to encode it.
+ */
+ addr = *mapData++;
+ if (addrWidth > 1)
+ addr |= (*mapData++) << 8;
+
+ if (debug)
+ LOGI(" addr=0x%04x ent=%d (aw=%d)", addr, entry, addrWidth);
+
+ addrDiff = addr - prevAddr;
+ assert(addrDiff > 0);
+ if (addrDiff < 8) {
+ /* small difference, encode in 3 bits */
+ key = addrDiff -1; /* set 00000AAA */
+ if (debug)
+ LOGI(" : small %d, key=0x%02x", addrDiff, key);
+ } else {
+ /* large difference, output escape code */
+ key = 0x07; /* escape code for AAA */
+ if (debug)
+ LOGI(" : large %d, key=0x%02x", addrDiff, key);
+ }
+
+ int numBitsChanged, firstBitChanged, lebSize;
+
+ lebSize = computeBitDiff(prevBits, mapData, regWidth,
+ &firstBitChanged, &numBitsChanged, NULL);
+
+ if (debug) {
+ LOGI(" : diff fbc=%d nbc=%d ls=%d (rw=%d)",
+ firstBitChanged, numBitsChanged, lebSize, regWidth);
+ }
+
+ if (numBitsChanged == 0) {
+ /* set B to 1 and CCCC to zero to indicate no bits were changed */
+ key |= 0x08;
+ if (debug) LOGI(" : no bits changed");
+ } else if (numBitsChanged == 1 && firstBitChanged < 16) {
+ /* set B to 0 and CCCC to the index of the changed bit */
+ key |= firstBitChanged << 4;
+ if (debug) LOGI(" : 1 low bit changed");
+ } else if (numBitsChanged < 15 && lebSize < regWidth) {
+ /* set B to 1 and CCCC to the number of bits */
+ key |= 0x08 | (numBitsChanged << 4);
+ if (debug) LOGI(" : some bits changed");
+ } else {
+ /* set B to 1 and CCCC to 0x0f so we store the entire vector */
+ key |= 0x08 | 0xf0;
+ if (debug) LOGI(" : encode original");
+ }
+
+ /*
+ * Encode output. Start with the key, follow with the address
+ * diff (if it didn't fit in 3 bits), then the changed bit info.
+ */
+ *tmpPtr++ = key;
+ if ((key & 0x07) == 0x07)
+ tmpPtr = writeUnsignedLeb128(tmpPtr, addrDiff);
+
+ if ((key & 0x08) != 0) {
+ int bitCount = key >> 4;
+ if (bitCount == 0) {
+ /* nothing changed, no additional output required */
+ } else if (bitCount == 15) {
+ /* full vector is most compact representation */
+ memcpy(tmpPtr, mapData, regWidth);
+ tmpPtr += regWidth;
+ } else {
+ /* write bit indices in LEB128 format */
+ (void) computeBitDiff(prevBits, mapData, regWidth,
+ NULL, NULL, tmpPtr);
+ tmpPtr += lebSize;
+ }
+ } else {
+ /* single-bit changed, value encoded in key byte */
+ }
+
+ prevBits = mapData;
+ prevAddr = addr;
+ mapData += regWidth;
+
+ /*
+ * See if we've run past the original size.
+ */
+ if (tmpPtr - tmpBuf.get() >= origSize) {
+ if (debug) {
+ LOGD("Compressed size >= original (%d vs %d): %s.%s",
+ tmpPtr - tmpBuf.get(), origSize,
+ meth->clazz->descriptor, meth->name);
+ }
+ return NULL;
+ }
+ }
+
+ /*
+ * Create a RegisterMap with the contents.
+ *
+ * TODO: consider using a threshold other than merely ">=". We would
+ * get poorer compression but potentially use less native heap space.
+ */
+ static const int kHeaderSize = offsetof(RegisterMap, data);
+ int newDataSize = tmpPtr - tmpBuf.get();
+ int newMapSize;
+
+ newMapSize = kHeaderSize + unsignedLeb128Size(newDataSize) + newDataSize;
+ if (newMapSize >= origSize) {
+ if (debug) {
+ LOGD("Final comp size >= original (%d vs %d): %s.%s",
+ newMapSize, origSize, meth->clazz->descriptor, meth->name);
+ }
+ return NULL;
+ }
+
+ pNewMap = (RegisterMap*) malloc(newMapSize);
+ if (pNewMap == NULL)
+ return NULL;
+ dvmRegisterMapSetFormat(pNewMap, kRegMapFormatDifferential);
+ dvmRegisterMapSetOnHeap(pNewMap, true);
+ dvmRegisterMapSetRegWidth(pNewMap, regWidth);
+ dvmRegisterMapSetNumEntries(pNewMap, numEntries);
+
+ tmpPtr = pNewMap->data;
+ tmpPtr = writeUnsignedLeb128(tmpPtr, newDataSize);
+ memcpy(tmpPtr, tmpBuf.get(), newDataSize);
+
+ if (REGISTER_MAP_VERBOSE) {
+ LOGD("Compression successful (%d -> %d) from aw=%d rw=%d ne=%d",
+ computeRegisterMapSize(pMap), computeRegisterMapSize(pNewMap),
+ addrWidth, regWidth, numEntries);
+ }
+
+ return pNewMap;
+}
+
+/*
+ * Toggle the value of the "idx"th bit in "ptr".
+ */
+static inline void toggleBit(u1* ptr, int idx)
+{
+ ptr[idx >> 3] ^= 1 << (idx & 0x07);
+}
+
+/*
+ * Expand a compressed map to an uncompressed form.
+ *
+ * Returns a newly-allocated RegisterMap on success, or NULL on failure.
+ *
+ * TODO: consider using the linear allocator or a custom allocator with
+ * LRU replacement for these instead of the native heap.
+ */
+static RegisterMap* uncompressMapDifferential(const RegisterMap* pMap)
+{
+ static const int kHeaderSize = offsetof(RegisterMap, data);
+ u1 format = dvmRegisterMapGetFormat(pMap);
+ RegisterMapFormat newFormat;
+ int regWidth, numEntries, newAddrWidth, newMapSize;
+
+ if (format != kRegMapFormatDifferential) {
+ LOGE("Not differential (%d)", format);
+ return NULL;
+ }
+
+ regWidth = dvmRegisterMapGetRegWidth(pMap);
+ numEntries = dvmRegisterMapGetNumEntries(pMap);
+
+ /* get the data size; we can check this at the end */
+ const u1* srcPtr = pMap->data;
+ int expectedSrcLen = readUnsignedLeb128(&srcPtr);
+ const u1* srcStart = srcPtr;
+
+ /* get the initial address and the 16-bit address flag */
+ int addr = *srcPtr & 0x7f;
+ if ((*srcPtr & 0x80) == 0) {
+ newFormat = kRegMapFormatCompact8;
+ newAddrWidth = 1;
+ } else {
+ newFormat = kRegMapFormatCompact16;
+ newAddrWidth = 2;
+ }
+ srcPtr++;
+
+ /* now we know enough to allocate the new map */
+ if (REGISTER_MAP_VERBOSE) {
+ LOGI("Expanding to map aw=%d rw=%d ne=%d",
+ newAddrWidth, regWidth, numEntries);
+ }
+ newMapSize = kHeaderSize + (newAddrWidth + regWidth) * numEntries;
+ RegisterMap* pNewMap = (RegisterMap*) malloc(newMapSize);
+
+ if (pNewMap == NULL)
+ return NULL;
+
+ dvmRegisterMapSetFormat(pNewMap, newFormat);
+ dvmRegisterMapSetOnHeap(pNewMap, true);
+ dvmRegisterMapSetRegWidth(pNewMap, regWidth);
+ dvmRegisterMapSetNumEntries(pNewMap, numEntries);
+
+ /*
+ * Write the start address and initial bits to the new map.
+ */
+ u1* dstPtr = pNewMap->data;
+
+ *dstPtr++ = addr & 0xff;
+ if (newAddrWidth > 1)
+ *dstPtr++ = (u1) (addr >> 8);
+
+ memcpy(dstPtr, srcPtr, regWidth);
+
+ int prevAddr = addr;
+ const u1* prevBits = dstPtr; /* point at uncompressed data */
+
+ dstPtr += regWidth;
+ srcPtr += regWidth;
+
+ /*
+ * Walk through, uncompressing one line at a time.
+ */
+ int entry;
+ for (entry = 1; entry < numEntries; entry++) {
+ int addrDiff;
+ u1 key;
+
+ key = *srcPtr++;
+
+ /* get the address */
+ if ((key & 0x07) == 7) {
+ /* address diff follows in ULEB128 */
+ addrDiff = readUnsignedLeb128(&srcPtr);
+ } else {
+ addrDiff = (key & 0x07) +1;
+ }
+
+ addr = prevAddr + addrDiff;
+ *dstPtr++ = addr & 0xff;
+ if (newAddrWidth > 1)
+ *dstPtr++ = (u1) (addr >> 8);
+
+ /* unpack the bits */
+ if ((key & 0x08) != 0) {
+ int bitCount = (key >> 4);
+ if (bitCount == 0) {
+ /* no bits changed, just copy previous */
+ memcpy(dstPtr, prevBits, regWidth);
+ } else if (bitCount == 15) {
+ /* full copy of bit vector is present; ignore prevBits */
+ memcpy(dstPtr, srcPtr, regWidth);
+ srcPtr += regWidth;
+ } else {
+ /* copy previous bits and modify listed indices */
+ memcpy(dstPtr, prevBits, regWidth);
+ while (bitCount--) {
+ int bitIndex = readUnsignedLeb128(&srcPtr);
+ toggleBit(dstPtr, bitIndex);
+ }
+ }
+ } else {
+ /* copy previous bits and modify the specified one */
+ memcpy(dstPtr, prevBits, regWidth);
+
+ /* one bit, from 0-15 inclusive, was changed */
+ toggleBit(dstPtr, key >> 4);
+ }
+
+ prevAddr = addr;
+ prevBits = dstPtr;
+ dstPtr += regWidth;
+ }
+
+ if (dstPtr - (u1*) pNewMap != newMapSize) {
+ LOGE("ERROR: output %d bytes, expected %d",
+ dstPtr - (u1*) pNewMap, newMapSize);
+ free(pNewMap);
+ return NULL;
+ }
+
+ if (srcPtr - srcStart != expectedSrcLen) {
+ LOGE("ERROR: consumed %d bytes, expected %d",
+ srcPtr - srcStart, expectedSrcLen);
+ free(pNewMap);
+ return NULL;
+ }
+
+ if (REGISTER_MAP_VERBOSE) {
+ LOGD("Expansion successful (%d -> %d)",
+ computeRegisterMapSize(pMap), computeRegisterMapSize(pNewMap));
+ }
+
+ return pNewMap;
+}