summaryrefslogtreecommitdiffstats
path: root/vm
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-02-10 15:43:57 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-02-10 15:43:57 -0800
commit5d709784bbf5001012d7f25172927d46f6c1abe1 (patch)
treea49c3dafdeed5037e5ad85aba23e5666b1faf57d /vm
parentbcd637a94f10b49d18b87a74a015f9d3453ed77a (diff)
downloadandroid_dalvik-5d709784bbf5001012d7f25172927d46f6c1abe1.tar.gz
android_dalvik-5d709784bbf5001012d7f25172927d46f6c1abe1.tar.bz2
android_dalvik-5d709784bbf5001012d7f25172927d46f6c1abe1.zip
auto import from //branches/cupcake/...@130745
Diffstat (limited to 'vm')
-rw-r--r--vm/DalvikVersion.h2
-rw-r--r--vm/Debugger.c3
-rw-r--r--vm/DvmDex.c12
-rw-r--r--vm/Globals.h2
-rw-r--r--vm/Init.c16
-rw-r--r--vm/Init.h3
-rw-r--r--vm/SignalCatcher.c3
-rw-r--r--vm/Sync.c2
-rw-r--r--vm/alloc/Heap.c19
-rw-r--r--vm/alloc/HeapInternal.h1
-rw-r--r--vm/alloc/HeapSource.c8
-rw-r--r--vm/analysis/CodeVerify.c277
-rw-r--r--vm/analysis/CodeVerify.h9
-rw-r--r--vm/analysis/DexOptimize.c15
-rw-r--r--vm/analysis/DexOptimize.h5
-rw-r--r--vm/analysis/DexVerify.c14
-rw-r--r--vm/analysis/DexVerify.h5
-rw-r--r--vm/analysis/RegisterMap.c305
-rw-r--r--vm/analysis/RegisterMap.h80
-rw-r--r--vm/hprof/Hprof.c112
-rw-r--r--vm/hprof/Hprof.h6
-rw-r--r--vm/hprof/HprofOutput.c5
-rw-r--r--vm/interp/Interp.c2
-rw-r--r--vm/mterp/armv5te/OP_FILLED_NEW_ARRAY.S8
-rw-r--r--vm/mterp/armv5te/footer.S2
-rw-r--r--vm/mterp/c/gotoTargets.c19
-rw-r--r--vm/mterp/out/InterpAsm-armv4.S18
-rw-r--r--vm/mterp/out/InterpAsm-armv5te.S18
-rw-r--r--vm/mterp/out/InterpAsm-x86.S19
-rw-r--r--vm/mterp/out/InterpC-allstubs.c19
-rw-r--r--vm/mterp/out/InterpC-portdbg.c19
-rw-r--r--vm/mterp/out/InterpC-portstd.c19
-rw-r--r--vm/mterp/out/InterpC-x86.c19
-rw-r--r--vm/mterp/x86/OP_FILLED_NEW_ARRAY.S9
-rw-r--r--vm/native/dalvik_system_VMDebug.c89
-rw-r--r--vm/oo/Class.c28
-rw-r--r--vm/oo/Class.h5
-rw-r--r--vm/oo/Object.h9
-rw-r--r--vm/oo/Resolve.c11
-rw-r--r--vm/reflect/Annotation.c12
40 files changed, 889 insertions, 340 deletions
diff --git a/vm/DalvikVersion.h b/vm/DalvikVersion.h
index 03d138230..d16daea70 100644
--- a/vm/DalvikVersion.h
+++ b/vm/DalvikVersion.h
@@ -31,6 +31,6 @@
* way classes load changes, e.g. field ordering or vtable layout. Changing
* this guarantees that the optimized form of the DEX file is regenerated.
*/
-#define DALVIK_VM_BUILD 13
+#define DALVIK_VM_BUILD 14
#endif /*_DALVIK_VERSION*/
diff --git a/vm/Debugger.c b/vm/Debugger.c
index f8e3b7516..c667893c3 100644
--- a/vm/Debugger.c
+++ b/vm/Debugger.c
@@ -2941,7 +2941,8 @@ void dvmDbgDdmDisconnected(void)
void dvmDbgDdmSendChunk(int type, int len, const u1* buf)
{
if (gDvm.jdwpState == NULL) {
- LOGI("Ignoring DDM send req for type=0x%08x len=%d\n", type, len);
+ LOGI("Debugger thread not active, ignoring DDM send (t=0x%08x l=%d)\n",
+ type, len);
return;
}
diff --git a/vm/DvmDex.c b/vm/DvmDex.c
index 137e00031..b5f8d02a3 100644
--- a/vm/DvmDex.c
+++ b/vm/DvmDex.c
@@ -117,8 +117,12 @@ int dvmDexFileOpenFromFd(int fd, DvmDex** ppDvmDex)
DvmDex* pDvmDex;
DexFile* pDexFile;
MemMapping memMap;
+ int parseFlags = kDexParseDefault;
int result = -1;
+ if (gDvm.verifyDexChecksum)
+ parseFlags |= kDexParseVerifyChecksum;
+
if (lseek(fd, 0, SEEK_SET) < 0) {
LOGE("lseek rewind failed\n");
goto bail;
@@ -129,7 +133,7 @@ int dvmDexFileOpenFromFd(int fd, DvmDex** ppDvmDex)
goto bail;
}
- pDexFile = dexFileParse(memMap.addr, memMap.length, kDexParseDefault);
+ pDexFile = dexFileParse(memMap.addr, memMap.length, parseFlags);
if (pDexFile == NULL) {
LOGE("DEX parse failed\n");
sysReleaseShmem(&memMap);
@@ -164,9 +168,13 @@ int dvmDexFileOpenPartial(const void* addr, int len, DvmDex** ppDvmDex)
{
DvmDex* pDvmDex;
DexFile* pDexFile;
+ int parseFlags = kDexParseDefault;
int result = -1;
- pDexFile = dexFileParse(addr, len, kDexParseDefault);
+ if (gDvm.verifyDexChecksum)
+ parseFlags |= kDexParseVerifyChecksum;
+
+ pDexFile = dexFileParse(addr, len, parseFlags);
if (pDexFile == NULL) {
LOGE("DEX parse failed\n");
goto bail;
diff --git a/vm/Globals.h b/vm/Globals.h
index 82d01aaf0..f68e45dd3 100644
--- a/vm/Globals.h
+++ b/vm/Globals.h
@@ -92,12 +92,14 @@ struct DvmGlobals {
int jniGrefLimit; // 0 means no limit
bool reduceSignals;
bool noQuitHandler;
+ bool verifyDexChecksum;
char* stackTraceFile; // for SIGQUIT-inspired output
bool logStdio;
DexOptimizerMode dexOptMode;
DexClassVerifyMode classVerifyMode;
+ bool generateRegisterMaps;
int assertionCtrlCount;
AssertionControl* assertionCtrl;
diff --git a/vm/Init.c b/vm/Init.c
index 036e36f5f..176910c82 100644
--- a/vm/Init.c
+++ b/vm/Init.c
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
/*
* Dalvik initialization, shutdown, and command-line argument processing.
*/
@@ -93,8 +94,10 @@ static void dvmUsage(const char* progName)
" -Xjnigreflimit:N (must be multiple of 100, >= 200)\n");
dvmFprintf(stderr, " -Xjniopts:{warnonly,forcecopy}\n");
dvmFprintf(stderr, " -Xdeadlockpredict:{off,warn,err,abort}\n");
- dvmFprintf(stderr, " -Xstacktracefile:<file name>");
- dvmFprintf(stderr, "\n\n");
+ dvmFprintf(stderr, " -Xstacktracefile:<filename>\n");
+ dvmFprintf(stderr, " -Xgenregmap\n");
+ dvmFprintf(stderr, " -Xcheckdexsum\n");
+ dvmFprintf(stderr, "\n");
dvmFprintf(stderr, "Configured with:"
#ifdef WITH_DEBUGGER
" debugger"
@@ -783,6 +786,12 @@ static int dvmProcessOptions(int argc, const char* const argv[],
} else if (strncmp(argv[i], "-Xstacktracefile:", 17) == 0) {
gDvm.stackTraceFile = strdup(argv[i]+17);
+ } else if (strcmp(argv[i], "-Xgenregmap") == 0) {
+ gDvm.generateRegisterMaps = true;
+
+ } else if (strcmp(argv[i], "-Xcheckdexsum") == 0) {
+ gDvm.verifyDexChecksum = true;
+
} else {
if (!ignoreUnrecognized) {
dvmFprintf(stderr, "Unrecognized option '%s'\n", argv[i]);
@@ -1248,7 +1257,7 @@ static bool dvmInitJDWP(void)
* Returns 0 on success.
*/
int dvmPrepForDexOpt(const char* bootClassPath, DexOptimizerMode dexOptMode,
- DexClassVerifyMode verifyMode)
+ DexClassVerifyMode verifyMode, int dexoptFlags)
{
gDvm.initializing = true;
gDvm.optimizing = true;
@@ -1264,6 +1273,7 @@ int dvmPrepForDexOpt(const char* bootClassPath, DexOptimizerMode dexOptMode,
/* set opt/verify modes */
gDvm.dexOptMode = dexOptMode;
gDvm.classVerifyMode = verifyMode;
+ gDvm.generateRegisterMaps = (dexoptFlags & DEXOPT_GEN_REGISTER_MAPS) != 0;
/*
* Initialize the heap, some basic thread control mutexes, and
diff --git a/vm/Init.h b/vm/Init.h
index 73d8fa664..854933834 100644
--- a/vm/Init.h
+++ b/vm/Init.h
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
/*
* VM initialization and shutdown.
*/
@@ -37,7 +38,7 @@ void dvmLateEnableAssertions(void);
* asked to optimize a DEX file holding fundamental classes.
*/
int dvmPrepForDexOpt(const char* bootClassPath, DexOptimizerMode dexOptMode,
- DexClassVerifyMode verifyMode);
+ DexClassVerifyMode verifyMode, int dexoptFlags);
/*
* Unconditionally abort the entire VM. Try not to use this.
diff --git a/vm/SignalCatcher.c b/vm/SignalCatcher.c
index 3efc19144..550f77796 100644
--- a/vm/SignalCatcher.c
+++ b/vm/SignalCatcher.c
@@ -158,6 +158,7 @@ static void logThreadStacks(void)
ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
printProcessName(&target);
dvmPrintDebugMessage(&target, "\n");
+ fflush(fp); /* emit at least the header if we crash during dump */
dvmDumpAllThreadsEx(&target, true);
fprintf(fp, "----- end %d -----\n", pid);
@@ -247,7 +248,7 @@ loop:
} else if (rcvd == SIGUSR1) {
#if WITH_HPROF
LOGI("SIGUSR1 forcing GC and HPROF dump\n");
- hprofDumpHeap();
+ hprofDumpHeap(NULL);
#else
LOGI("SIGUSR1 forcing GC (no HPROF)\n");
dvmCollectGarbage(false);
diff --git a/vm/Sync.c b/vm/Sync.c
index 96789c277..1f813490c 100644
--- a/vm/Sync.c
+++ b/vm/Sync.c
@@ -527,7 +527,7 @@ static void waitMonitor(Thread* self, Monitor* mon, s8 msec, s4 nsec,
* situation, but we need to handle it correctly.)
*/
if (mon->notifying + mon->interrupting > mon->waiting) {
- LOGI("threadid=%d: bogus mon %d+%d>%d; adjusting\n",
+ LOGD("threadid=%d: bogus mon %d+%d>%d; adjusting\n",
self->threadId, mon->notifying, mon->interrupting,
mon->waiting);
diff --git a/vm/alloc/Heap.c b/vm/alloc/Heap.c
index 4129a90b3..9ddc8be82 100644
--- a/vm/alloc/Heap.c
+++ b/vm/alloc/Heap.c
@@ -829,11 +829,20 @@ void dvmCollectGarbageInternal(bool collectSoftReferences)
#endif
if (gcHeap->hprofDumpOnGc) {
- gcHeap->hprofContext = hprofStartup("/data/misc");
+ char nameBuf[128];
+
+ if (gcHeap->hprofFileName == NULL) {
+ /* no filename was provided; invent one */
+ sprintf(nameBuf, "/data/misc/heap-dump-tm%d-pid%d.hprof",
+ (int) time(NULL), (int) getpid());
+ gcHeap->hprofFileName = nameBuf;
+ }
+ gcHeap->hprofContext = hprofStartup(gcHeap->hprofFileName);
if (gcHeap->hprofContext != NULL) {
hprofStartHeapDump(gcHeap->hprofContext);
}
gcHeap->hprofDumpOnGc = false;
+ gcHeap->hprofFileName = NULL;
}
#endif
@@ -1033,11 +1042,17 @@ void dvmCollectGarbageInternal(bool collectSoftReferences)
}
#if WITH_HPROF
-void hprofDumpHeap()
+/*
+ * Perform garbage collection, writing heap information to the specified file.
+ *
+ * If "fileName" is NULL, a suitable name will be generated automatically.
+ */
+void hprofDumpHeap(const char* fileName)
{
dvmLockMutex(&gDvm.gcHeapLock);
gDvm.gcHeap->hprofDumpOnGc = true;
+ gDvm.gcHeap->hprofFileName = fileName;
dvmCollectGarbageInternal(false);
dvmUnlockMutex(&gDvm.gcHeapLock);
diff --git a/vm/alloc/HeapInternal.h b/vm/alloc/HeapInternal.h
index e3b212d8d..78519830d 100644
--- a/vm/alloc/HeapInternal.h
+++ b/vm/alloc/HeapInternal.h
@@ -187,6 +187,7 @@ struct GcHeap {
#if WITH_HPROF
bool hprofDumpOnGc;
+ const char* hprofFileName;
hprof_context_t *hprofContext;
#endif
};
diff --git a/vm/alloc/HeapSource.c b/vm/alloc/HeapSource.c
index 58952e8c6..d97290f32 100644
--- a/vm/alloc/HeapSource.c
+++ b/vm/alloc/HeapSource.c
@@ -17,6 +17,7 @@
#include <cutils/mspace.h>
#include <limits.h> // for INT_MAX
#include <sys/mman.h>
+#include <errno.h>
#include "Dalvik.h"
#include "alloc/Heap.h"
@@ -298,6 +299,7 @@ createMspace(size_t startSize, size_t absoluteMaxSize, size_t id)
* than the starting size.
*/
LOGV_HEAP("Creating VM heap of size %u\n", startSize);
+ errno = 0;
msp = create_contiguous_mspace_with_name(startSize/2,
absoluteMaxSize, /*locked=*/false, name);
if (msp != NULL) {
@@ -306,7 +308,11 @@ createMspace(size_t startSize, size_t absoluteMaxSize, size_t id)
*/
mspace_set_max_allowed_footprint(msp, startSize);
} else {
- LOGE_HEAP("Can't create VM heap of size %u\n", startSize/2);
+ /* There's no guarantee that errno has meaning when the call
+ * fails, but it often does.
+ */
+ LOGE_HEAP("Can't create VM heap of size (%u,%u) (errno=%d)\n",
+ startSize/2, absoluteMaxSize, errno);
}
return msp;
diff --git a/vm/analysis/CodeVerify.c b/vm/analysis/CodeVerify.c
index a57328745..65aa8338d 100644
--- a/vm/analysis/CodeVerify.c
+++ b/vm/analysis/CodeVerify.c
@@ -29,6 +29,7 @@
*/
#include "Dalvik.h"
#include "analysis/CodeVerify.h"
+#include "analysis/RegisterMap.h"
#include "libdex/DexCatch.h"
#include "libdex/InstrUtils.h"
@@ -36,16 +37,22 @@
/*
- * If defined, store registers for all instructions, not just branch
- * targets. Increases memory usage and adds to CPU load. Only necessary
- * when generating data for exact GC.
+ * We don't need to store the register data for many instructions, because
+ * we either only need it at branch points (for verification) or GC points
+ * and branches (for verification + type-precise register analysis).
*/
-#define USE_FULL_TABLE false
+typedef enum RegisterTrackingMode {
+ kTrackRegsBranches,
+ kTrackRegsGcPoints,
+ kTrackRegsAll
+} RegisterTrackingMode;
/*
* Set this to enable dead code scanning. This is not required, but it's
* very useful when testing changes to the verifier (to make sure we're not
* skipping over stuff) and for checking the optimized output from "dx".
+ * The only reason not to do it is that it slightly increases the time
+ * required to perform verification.
*/
#define DEAD_CODE_SCAN true
@@ -82,21 +89,13 @@ static inline bool doVerboseLogging(const Method* meth) {
#define RESULT_REGISTER(_insnRegCount) (_insnRegCount)
/*
- * RegType holds information about the type of data held in a register.
- * For most types it's a simple enum. For reference types it holds a
- * pointer to the ClassObject, and for uninitialized references it holds
- * an index into the UninitInstanceMap.
- */
-typedef u4 RegType;
-
-/*
* Big fat collection of registers.
*/
typedef struct RegisterTable {
/*
* Array of RegType arrays, one per address in the method. We only
- * set the pointers for addresses that are branch targets unless
- * USE_FULL_TABLE is true.
+ * set the pointers for certain addresses, based on what we're trying
+ * to accomplish.
*/
RegType** addrRegs;
@@ -104,7 +103,7 @@ typedef struct RegisterTable {
* Number of registers we track for each instruction. This is equal
* to the method's declared "registersSize" plus kExtraRegs.
*/
- int insnRegCount;
+ int insnRegCountPlus;
/*
* A single large alloc, with all of the storage needed for addrRegs.
@@ -293,37 +292,34 @@ static bool canConvertTo2(RegType srcType, RegType checkType)
}
/*
- * Determine whether or not "srcType" and "checkType" have the same width.
- * The idea is to determine whether or not it's okay to use a sub-integer
- * instruction (say, sget-short) with a primitive field of a specific type.
+ * Determine whether or not "instrType" and "targetType" are compatible,
+ * for purposes of getting or setting a value in a field or array. The
+ * idea is that an instruction with a category 1nr type (say, aget-short
+ * or iput-boolean) is accessing a static field, instance field, or array
+ * entry, and we want to make sure sure that the operation is legal.
+ *
+ * At a minimum, source and destination must have the same width. We
+ * further refine this to assert that "short" and "char" are not
+ * compatible, because the sign-extension is different on the "get"
+ * operations. As usual, "float" and "int" are interoperable.
*
- * We should always be passed "definite" types here; pseudo-types like
- * kRegTypeZero are not expected. We could get kRegTypeUnknown if a type
- * lookup failed.
+ * We're not considering the actual contents of the register, so we'll
+ * never get "pseudo-types" like kRegTypeZero or kRegTypePosShort. We
+ * could get kRegTypeUnknown in "targetType" if a field or array class
+ * lookup failed. Category 2 types and references are checked elsewhere.
*/
-static bool isTypeWidthEqual1nr(RegType type1, RegType type2)
+static bool checkFieldArrayStore1nr(RegType instrType, RegType targetType)
{
- /* primitive sizes; we use zero for boolean so it's not equal to others */
- static const char sizeTab[kRegType1nrEND-kRegType1nrSTART+1] = {
- /* F 0 1 Z b B s S C I */
- 4, 8, 9, 0, 1, 1, 2, 2, 2, 4
- };
-
- assert(type1 != kRegTypeZero && type1 != kRegTypeOne &&
- type1 != kRegTypePosByte && type1 != kRegTypePosShort);
- assert(type2 != kRegTypeZero && type2 != kRegTypeOne &&
- type2 != kRegTypePosByte && type2 != kRegTypePosShort);
-
- if (type1 == type2)
+ if (instrType == targetType)
return true; /* quick positive; most common case */
- /* might be able to eliminate one of these by narrowly defining our args? */
- if (type1 < kRegType1nrSTART || type1 > kRegType1nrEND)
- return false;
- if (type2 < kRegType1nrSTART || type2 > kRegType1nrEND)
- return false;
+ if ((instrType == kRegTypeInteger && targetType == kRegTypeFloat) ||
+ (instrType == kRegTypeFloat && targetType == kRegTypeInteger))
+ {
+ return true;
+ }
- return sizeTab[type1-kRegType1nrSTART] == sizeTab[type2-kRegType1nrSTART];
+ return false;
}
/*
@@ -1476,9 +1472,17 @@ static void setRegisterType(RegType* insnRegs, const int insnRegCount,
}
insnRegs[vdst] = newType;
- /* if it's an initialized ref, make sure it's not a prim class */
- assert(regTypeIsUninitReference(newType) ||
- !dvmIsPrimitiveClass(regTypeInitializedReferenceToClass(newType)));
+ /*
+ * In most circumstances we won't see a reference to a primitive
+ * class here (e.g. "D"), since that would mean the object in the
+ * register is actually a primitive type. It can happen as the
+ * result of an assumed-successful check-cast instruction in
+ * which the second argument refers to a primitive class. (In
+ * practice, such an instruction will always throw an exception.)
+ *
+ * This is not an issue for instructions like const-class, where
+ * the object in the register is a java.lang.Class instance.
+ */
break;
}
/* bad - fall through */
@@ -2443,20 +2447,22 @@ static void checkFinalFieldAccess(const Method* meth, const Field* field,
return;
}
+ /*
+ * The EMMA code coverage tool generates a static method that
+ * modifies a private static final field. The method is only
+ * called by <clinit>, so the code is reasonable if not quite
+ * kosher. (Attempting to *compile* code that does something
+ * like that will earn you a quick thumbs-down from javac.)
+ *
+ * The verifier in another popular VM doesn't complain about this,
+ * so we're going to allow classes to modify their own static
+ * final fields outside of class initializers. Further testing
+ * showed that modifications to instance fields are also allowed.
+ */
+#if 0
/* make sure we're in the right kind of constructor */
if (dvmIsStaticField(field)) {
- /*
- * The EMMA code coverage tool generates a static method that
- * modifies a private static final field. The method is only
- * called by <clinit>, so the code is reasonable if not quite
- * kosher. (Attempting to *compile* code that does something
- * like that will earn you a quick thumbs-down from javac.)
- *
- * The verifier in another popular VM doesn't complain about this,
- * so we're going to allow classes to modify their own static
- * final fields outside of class initializers.
- */
- if (false && !isClassInitMethod(meth)) {
+ if (!isClassInitMethod(meth)) {
LOG_VFY_METH(meth,
"VFY: can't modify final static field outside <clinit>\n");
*pOkay = false;
@@ -2468,6 +2474,7 @@ static void checkFinalFieldAccess(const Method* meth, const Field* field,
*pOkay = false;
}
}
+#endif
}
/*
@@ -2627,71 +2634,91 @@ static ClassObject* getCaughtExceptionType(const Method* meth, int insnIdx)
* information to kRegTypeUnknown.
*/
static bool initRegisterTable(const Method* meth, const InsnFlags* insnFlags,
- RegisterTable* regTable)
+ RegisterTable* regTable, RegisterTrackingMode trackRegsFor)
{
const int insnsSize = dvmGetMethodInsnsSize(meth);
int i;
- regTable->insnRegCount = meth->registersSize + kExtraRegs;
+ regTable->insnRegCountPlus = meth->registersSize + kExtraRegs;
regTable->addrRegs = (RegType**) calloc(insnsSize, sizeof(RegType*));
if (regTable->addrRegs == NULL)
return false;
+ assert(insnsSize > 0);
+
/*
- * "Full" means "every address that holds the start of an instruction".
- * "Not full" means "every address that can be branched to".
+ * "All" means "every address that holds the start of an instruction".
+ * "Branches" and "GcPoints" mean just those addresses.
*
- * "Full" seems to require > 6x the memory on average. Fortunately we
- * don't need to hold on to it for very long.
+ * "GcPoints" fills about half the addresses, "Branches" about 15%.
*/
- if (USE_FULL_TABLE) {
- int insnCount = 0;
+ int interestingCount = 0;
+ //int insnCount = 0;
- for (i = 0; i < insnsSize; i++) {
- if (dvmInsnIsOpcode(insnFlags, i))
- insnCount++;
- }
+ for (i = 0; i < insnsSize; i++) {
+ bool interesting;
- regTable->regAlloc = (RegType*)
- calloc(regTable->insnRegCount * insnCount, sizeof(RegType));
- if (regTable->regAlloc == NULL)
+ switch (trackRegsFor) {
+ case kTrackRegsAll:
+ interesting = dvmInsnIsOpcode(insnFlags, i);
+ break;
+ case kTrackRegsGcPoints:
+ interesting = dvmInsnIsGcPoint(insnFlags, i) ||
+ dvmInsnIsBranchTarget(insnFlags, i);
+ break;
+ case kTrackRegsBranches:
+ interesting = dvmInsnIsBranchTarget(insnFlags, i);
+ break;
+ default:
+ dvmAbort();
return false;
-
- RegType* regPtr = regTable->regAlloc;
- for (i = 0; i < insnsSize; i++) {
- if (dvmInsnIsOpcode(insnFlags, i)) {
- regTable->addrRegs[i] = regPtr;
- regPtr += regTable->insnRegCount;
- }
}
- assert(regPtr - regTable->regAlloc ==
- regTable->insnRegCount * insnCount);
- } else {
- int branchCount = 0;
- for (i = 0; i < insnsSize; i++) {
- if (dvmInsnIsBranchTarget(insnFlags, i))
- branchCount++;
- }
- assert(branchCount > 0);
+ if (interesting)
+ interestingCount++;
- regTable->regAlloc = (RegType*)
- calloc(regTable->insnRegCount * branchCount, sizeof(RegType));
- if (regTable->regAlloc == NULL)
- return false;
+ /* count instructions, for display only */
+ //if (dvmInsnIsOpcode(insnFlags, i))
+ // insnCount++;
+ }
- RegType* regPtr = regTable->regAlloc;
- for (i = 0; i < insnsSize; i++) {
- if (dvmInsnIsBranchTarget(insnFlags, i)) {
- regTable->addrRegs[i] = regPtr;
- regPtr += regTable->insnRegCount;
- }
+ regTable->regAlloc = (RegType*)
+ calloc(regTable->insnRegCountPlus * interestingCount, sizeof(RegType));
+ if (regTable->regAlloc == NULL)
+ return false;
+
+ RegType* regPtr = regTable->regAlloc;
+ for (i = 0; i < insnsSize; i++) {
+ bool interesting;
+
+ switch (trackRegsFor) {
+ case kTrackRegsAll:
+ interesting = dvmInsnIsOpcode(insnFlags, i);
+ break;
+ case kTrackRegsGcPoints:
+ interesting = dvmInsnIsGcPoint(insnFlags, i) ||
+ dvmInsnIsBranchTarget(insnFlags, i);
+ break;
+ case kTrackRegsBranches:
+ interesting = dvmInsnIsBranchTarget(insnFlags, i);
+ break;
+ default:
+ dvmAbort();
+ return false;
}
- assert(regPtr - regTable->regAlloc ==
- regTable->insnRegCount * branchCount);
+ if (interesting) {
+ regTable->addrRegs[i] = regPtr;
+ regPtr += regTable->insnRegCountPlus;
+ }
}
+ //LOGD("Tracking registers for %d, total %d of %d(%d) (%d%%)\n",
+ // TRACK_REGS_FOR, interestingCount, insnCount, insnsSize,
+ // (interestingCount*100) / insnCount);
+
+ assert(regPtr - regTable->regAlloc ==
+ regTable->insnRegCountPlus * interestingCount);
assert(regTable->addrRegs[0] != NULL);
return true;
}
@@ -2715,12 +2742,10 @@ static void verifyFilledNewArrayRegs(const Method* meth,
assert(dvmIsArrayClass(resClass));
elemType = resClass->elementClass->primitiveType;
if (elemType == PRIM_NOT) {
- LOG_VFY("VFY: filled-new-array not yet supported on reference types\n");
- *pOkay = false;
- return;
+ expectedType = regTypeFromClass(resClass->elementClass);
+ } else {
+ expectedType = primitiveTypeToRegType(elemType);
}
-
- expectedType = primitiveTypeToRegType(elemType);
//LOGI("filled-new-array: %s -> %d\n", resClass->descriptor, expectedType);
/*
@@ -2760,6 +2785,7 @@ bool dvmVerifyCodeFlow(const Method* meth, InsnFlags* insnFlags,
bool result = false;
const int insnsSize = dvmGetMethodInsnsSize(meth);
const u2* insns = meth->insns;
+ const bool generateRegisterMap = gDvm.generateRegisterMaps;
int i, offset;
bool isConditional;
RegisterTable regTable;
@@ -2767,7 +2793,7 @@ bool dvmVerifyCodeFlow(const Method* meth, InsnFlags* insnFlags,
memset(&regTable, 0, sizeof(regTable));
#ifndef NDEBUG
- checkMergeTab(); // only need to do this when table changes
+ checkMergeTab(); // only need to do this if table gets updated
#endif
/*
@@ -2796,9 +2822,12 @@ bool dvmVerifyCodeFlow(const Method* meth, InsnFlags* insnFlags,
}
/*
- * Create register lists, and initialize them to "Unknown".
+ * Create register lists, and initialize them to "Unknown". If we're
+ * also going to create the register map, we need to retain the
+ * register lists for a larger set of addresses.
*/
- if (!initRegisterTable(meth, insnFlags, &regTable))
+ if (!initRegisterTable(meth, insnFlags, &regTable,
+ generateRegisterMap ? kTrackRegsGcPoints : kTrackRegsBranches))
goto bail;
/*
@@ -2815,6 +2844,30 @@ bool dvmVerifyCodeFlow(const Method* meth, InsnFlags* insnFlags,
goto bail;
/*
+ * Generate a register map.
+ */
+ if (generateRegisterMap) {
+ RegisterMap* pMap;
+ VerifierData vd;
+
+ vd.method = meth;
+ vd.insnsSize = insnsSize;
+ vd.insnRegCount = meth->registersSize;
+ vd.insnFlags = insnFlags;
+ vd.addrRegs = regTable.addrRegs;
+
+ pMap = dvmGenerateRegisterMapV(&vd);
+ if (pMap != NULL) {
+ /*
+ * Tuck it into the Method struct. It will either get used
+ * directly or, if we're in dexopt, will be packed up and
+ * appended to the DEX file.
+ */
+ dvmSetRegisterMap((Method*)meth, pMap);
+ }
+ }
+
+ /*
* Success.
*/
result = true;
@@ -3776,7 +3829,7 @@ aget_1nr_common:
srcType = primitiveTypeToRegType(
resClass->elementClass->primitiveType);
- if (!isTypeWidthEqual1nr(tmpType, srcType)) {
+ if (!checkFieldArrayStore1nr(tmpType, srcType)) {
LOG_VFY("VFY: invalid aget-1nr, array type=%d with"
" inst type=%d (on %s)\n",
srcType, tmpType, resClass->descriptor);
@@ -3961,7 +4014,7 @@ aput_1nr_common:
resClass->elementClass->primitiveType);
assert(dstType != kRegTypeUnknown);
- if (!isTypeWidthEqual1nr(tmpType, dstType)) {
+ if (!checkFieldArrayStore1nr(tmpType, dstType)) {
LOG_VFY("VFY: invalid aput-1nr on %s (inst=%d dst=%d)\n",
resClass->descriptor, tmpType, dstType);
okay = false;
@@ -4108,7 +4161,7 @@ iget_1nr_common:
/* make sure the field's type is compatible with expectation */
fieldType = primSigCharToRegType(instField->field.signature[0]);
if (fieldType == kRegTypeUnknown ||
- !isTypeWidthEqual1nr(tmpType, fieldType))
+ !checkFieldArrayStore1nr(tmpType, fieldType))
{
LOG_VFY("VFY: invalid iget-1nr of %s.%s (inst=%d field=%d)\n",
instField->field.clazz->descriptor,
@@ -4232,7 +4285,7 @@ iput_1nr_common:
/* get type of field we're storing into */
fieldType = primSigCharToRegType(instField->field.signature[0]);
if (fieldType == kRegTypeUnknown ||
- !isTypeWidthEqual1nr(tmpType, fieldType))
+ !checkFieldArrayStore1nr(tmpType, fieldType))
{
LOG_VFY("VFY: invalid iput-1nr of %s.%s (inst=%d field=%d)\n",
instField->field.clazz->descriptor,
@@ -4376,7 +4429,7 @@ sget_1nr_common:
* because e.g. "int" and "float" are interchangeable.)
*/
fieldType = primSigCharToRegType(staticField->field.signature[0]);
- if (!isTypeWidthEqual1nr(tmpType, fieldType)) {
+ if (!checkFieldArrayStore1nr(tmpType, fieldType)) {
LOG_VFY("VFY: invalid sget-1nr of %s.%s (inst=%d actual=%d)\n",
staticField->field.clazz->descriptor,
staticField->field.name, tmpType, fieldType);
@@ -4486,7 +4539,7 @@ sput_1nr_common:
* can lead to trouble if we do 16-bit writes.
*/
fieldType = primSigCharToRegType(staticField->field.signature[0]);
- if (!isTypeWidthEqual1nr(tmpType, fieldType)) {
+ if (!checkFieldArrayStore1nr(tmpType, fieldType)) {
LOG_VFY("VFY: invalid sput-1nr of %s.%s (inst=%d actual=%d)\n",
staticField->field.clazz->descriptor,
staticField->field.name, tmpType, fieldType);
diff --git a/vm/analysis/CodeVerify.h b/vm/analysis/CodeVerify.h
index 487acfb0d..0cd463895 100644
--- a/vm/analysis/CodeVerify.h
+++ b/vm/analysis/CodeVerify.h
@@ -98,6 +98,15 @@ enum {
#define kRegTypeUninitMask 0xff
#define kRegTypeUninitShift 8
+/*
+ * RegType holds information about the type of data held in a register.
+ * For most types it's a simple enum. For reference types it holds a
+ * pointer to the ClassObject, and for uninitialized references it holds
+ * an index into the UninitInstanceMap.
+ */
+typedef u4 RegType;
+
+/* table with merge logic for primitive types */
extern const char gDvmMergeTab[kRegTypeMAX][kRegTypeMAX];
diff --git a/vm/analysis/DexOptimize.c b/vm/analysis/DexOptimize.c
index 89f3aa14d..a87d78947 100644
--- a/vm/analysis/DexOptimize.c
+++ b/vm/analysis/DexOptimize.c
@@ -321,6 +321,9 @@ bool dvmOptimizeDexFile(int fd, off_t dexOffset, long dexLength,
* want to avoid this.
*
* For optimization and/or verification, we need to load all the classes.
+ *
+ * We don't check gDvm.generateRegisterMaps, since that is dependent
+ * upon the verifier state.
*/
if (gDvm.classVerifyMode == VERIFY_MODE_NONE &&
(gDvm.dexOptMode == OPTIMIZE_MODE_NONE ||
@@ -423,6 +426,8 @@ bool dvmOptimizeDexFile(int fd, off_t dexOffset, long dexLength,
}
if (isBootstrap)
flags |= DEXOPT_IS_BOOTSTRAP;
+ if (gDvm.generateRegisterMaps)
+ flags |= DEXOPT_GEN_REGISTER_MAP;
sprintf(values[9], "%d", flags);
argv[curArg++] = values[9];
@@ -1627,9 +1632,13 @@ ClassObject* dvmOptResolveClass(ClassObject* referrer, u4 classIdx)
*/
resClass = dvmDexGetResolvedClass(pDvmDex, classIdx);
if (resClass == NULL) {
- resClass = dvmFindClassNoInit(
- dexStringByTypeIdx(pDvmDex->pDexFile, classIdx),
- referrer->classLoader);
+ const char* className = dexStringByTypeIdx(pDvmDex->pDexFile, classIdx);
+ if (className[0] != '\0' && className[1] == '\0') {
+ /* primitive type */
+ resClass = dvmFindPrimitiveClass(className[0]);
+ } else {
+ resClass = dvmFindClassNoInit(className, referrer->classLoader);
+ }
if (resClass == NULL) {
/* not found, exception should be raised */
LOGV("DexOpt: class %d (%s) not found\n",
diff --git a/vm/analysis/DexOptimize.h b/vm/analysis/DexOptimize.h
index ecb71d32f..01aa8288a 100644
--- a/vm/analysis/DexOptimize.h
+++ b/vm/analysis/DexOptimize.h
@@ -30,6 +30,11 @@ typedef enum DexOptimizerMode {
OPTIMIZE_MODE_ALL /* optimize all classes */
} DexOptimizerMode;
+/* some additional bit flags for dexopt */
+enum DexoptFlags {
+ DEXOPT_GEN_REGISTER_MAPS = 1, /* generate register maps during verify */
+};
+
/*
* Given the full path to a DEX or Jar file, and (if appropriate) the name
* within the Jar, open the optimized version from the cache.
diff --git a/vm/analysis/DexVerify.c b/vm/analysis/DexVerify.c
index 5a3e8bdad..354d68f11 100644
--- a/vm/analysis/DexVerify.c
+++ b/vm/analysis/DexVerify.c
@@ -527,15 +527,23 @@ static bool verifyInstructions(const Method* meth, InsnFlags* insnFlags,
{
const int insnCount = dvmGetMethodInsnsSize(meth);
const u2* insns = meth->insns;
- int i, width, offset, absOffset;
+ int i;
/* the start of the method is a "branch target" */
dvmInsnSetBranchTarget(insnFlags, 0, true);
for (i = 0; i < insnCount; /**/) {
- width = dvmInsnGetWidth(insnFlags, i);
+ static int gcMask = kInstrCanBranch | kInstrCanSwitch |
+ kInstrCanThrow | kInstrCanReturn;
+ int width = dvmInsnGetWidth(insnFlags, i);
+ OpCode opcode = *insns & 0xff;
+ InstructionFlags opFlags = dexGetInstrFlags(gDvm.instrFlags, opcode);
+ int offset, absOffset;
+
+ if ((opFlags & gcMask) != 0)
+ dvmInsnSetGcPoint(insnFlags, i, true);
- switch (*insns & 0xff) {
+ switch (opcode) {
case OP_NOP:
/* plain no-op or switch table data; nothing to do here */
break;
diff --git a/vm/analysis/DexVerify.h b/vm/analysis/DexVerify.h
index 4b0d6933d..9deaad9f5 100644
--- a/vm/analysis/DexVerify.h
+++ b/vm/analysis/DexVerify.h
@@ -57,4 +57,9 @@ bool dvmVerifyAllClasses(DexFile* pDexFile);
*/
bool dvmVerifyClass(ClassObject* clazz, int verifyFlags);
+/*
+ * Release the storage associated with a RegisterMap.
+ */
+void dvmFreeRegisterMap(RegisterMap* pMap);
+
#endif /*_DALVIK_DEXVERIFY*/
diff --git a/vm/analysis/RegisterMap.c b/vm/analysis/RegisterMap.c
index eb243af2a..b02874a75 100644
--- a/vm/analysis/RegisterMap.c
+++ b/vm/analysis/RegisterMap.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -28,20 +28,41 @@
#include "libdex/DexCatch.h"
#include "libdex/InstrUtils.h"
+#include <stddef.h>
-/*
-Notes on RegisterMap vs. verification
-
-Much of this is redundant with the bytecode verifier. Unfortunately we
-can't do this at verification time unless we're willing to store the
-results in the optimized DEX file, which increases their size by about 25%
-unless we use compression, and for performance reasons we don't want to
-just re-run the verifier.
-On the plus side, we know that verification has completed successfully --
-or at least are allowed to assume that it would -- so we skip a lot of
-the checks (like verifying that the register indices in instructions
-are reasonable).
+/*
+Notes on just-in-time RegisterMap generation
+
+Generating RegisterMap tables as part of verification is convenient because
+we generate most of what we need to know as part of doing the verify.
+The negative aspect of doing it this way is that we must store the
+result in the DEX file (if we're verifying ahead of time) or in memory
+(if verifying during class load) for every concrete non-native method,
+even if we never actually need the map during a GC.
+
+A simple but compact encoding of register map data increases the size of
+optimized DEX files by about 25%, so size considerations are important.
+
+We can instead generate the RegisterMap at the point where it is needed.
+In a typical application we only need to convert about 2% of the loaded
+methods, and we can generate type-precise roots reasonably quickly because
+(a) we know the method has already been verified and hence can make a
+lot of assumptions, and (b) we don't care what type of object a register
+holds, just whether or not it holds a reference, and hence can skip a
+lot of class resolution gymnastics.
+
+There are a couple of problems with this approach however. First, to
+get good performance we really want an implementation that is largely
+independent from the verifier, which means some duplication of effort.
+Second, we're dealing with post-dexopt code, which contains "quickened"
+instructions. We can't process those without either tracking type
+information (which slows us down) or storing additional data in the DEX
+file that allows us to reconstruct the original instructions (adds ~5%
+to the size of the ODEX).
+
+
+Implementation notes...
Both type-precise and live-precise information can be generated knowing
only whether or not a register holds a reference. We don't need to
@@ -50,15 +71,250 @@ Not only can we skip many of the fancy steps in the verifier, we can
initialize from simpler sources, e.g. the initial registers and return
type are set from the "shorty" signature rather than the full signature.
-The short-term storage needs are different; for example, the verifier
-stores 4-byte RegType values for every address that can be a branch
-target, while we store 1-byte SRegType values for every address that
-can be a GC point. There are many more GC points than branch targets.
-We could use a common data type here, but for larger methods it can mean
-the difference between 300KB and 1.2MB.
+The short-term storage needs for just-in-time register map generation can
+be much lower because we can use a 1-byte SRegType instead of a 4-byte
+RegType. On the other hand, if we're not doing type-precise analysis
+in the verifier we only need to store register contents at every branch
+target, rather than every GC point (which are much more frequent).
+Whether it happens in the verifier or independently, because this is done
+with native heap allocations that may be difficult to return to the system,
+an effort should be made to minimize memory use.
*/
+// fwd
+static void outputTypeVector(const RegType* regs, int insnRegCount, u1* data);
+static bool verifyMap(VerifierData* vdata, const RegisterMap* pMap);
+
+/*
+ * 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)
+{
+ RegisterMap* pMap = NULL;
+ RegisterMap* pResult = NULL;
+ RegisterMapFormat format;
+ u1 regWidth;
+ u1* mapData;
+ int i, bytesForAddr, gcPointCount;
+ int bufSize;
+
+ regWidth = (vdata->method->registersSize + 7) / 8;
+ if (vdata->insnsSize < 256) {
+ format = kFormatCompact8;
+ bytesForAddr = 1;
+ } else {
+ format = kFormatCompact16;
+ 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 < 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\n",
+ gcPointCount);
+ goto bail;
+ }
+
+ /*
+ * Allocate a buffer to hold the map data.
+ */
+ bufSize = offsetof(RegisterMap, data);
+ bufSize += gcPointCount * (bytesForAddr + regWidth);
+
+ LOGD("+++ grm: %s.%s (adr=%d gpc=%d rwd=%d bsz=%d)\n",
+ vdata->method->clazz->descriptor, vdata->method->name,
+ bytesForAddr, gcPointCount, regWidth, bufSize);
+
+ pMap = (RegisterMap*) malloc(bufSize);
+ pMap->format = format;
+ pMap->regWidth = regWidth;
+ pMap->numEntries = gcPointCount;
+
+ /*
+ * Populate it.
+ */
+ mapData = pMap->data;
+ for (i = 0; i < vdata->insnsSize; i++) {
+ if (dvmInsnIsGcPoint(vdata->insnFlags, i)) {
+ assert(vdata->addrRegs[i] != NULL);
+ if (format == kFormatCompact8) {
+ *mapData++ = i;
+ } else /*kFormatCompact16*/ {
+ *mapData++ = i & 0xff;
+ *mapData++ = i >> 8;
+ }
+ outputTypeVector(vdata->addrRegs[i], vdata->insnRegCount, mapData);
+ mapData += regWidth;
+ }
+ }
+
+ LOGI("mapData=%p pMap=%p bufSize=%d\n", mapData, pMap, bufSize);
+ assert(mapData - (const u1*) pMap == bufSize);
+
+#if 1
+ if (!verifyMap(vdata, pMap))
+ goto bail;
+#endif
+
+ pResult = pMap;
+
+bail:
+ return pResult;
+}
+
+/*
+ * Release the storage held by a RegisterMap.
+ */
+void dvmFreeRegisterMap(RegisterMap* pMap)
+{
+ if (pMap == NULL)
+ return;
+
+ 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;
+ }
+}
+
+/*
+ * Double-check the map.
+ *
+ * We run through all of the data in the map, and compare it to the original.
+ */
+static bool verifyMap(VerifierData* vdata, const RegisterMap* pMap)
+{
+ const u1* data = pMap->data;
+ int ent;
+
+ for (ent = 0; ent < pMap->numEntries; ent++) {
+ int addr;
+
+ switch (pMap->format) {
+ case kFormatCompact8:
+ addr = *data++;
+ break;
+ case kFormatCompact16:
+ addr = *data++;
+ addr |= (*data++) << 8;
+ break;
+ default:
+ /* shouldn't happen */
+ LOGE("GLITCH: bad format (%d)", pMap->format);
+ dvmAbort();
+ }
+
+ const RegType* regs = vdata->addrRegs[addr];
+ if (regs == NULL) {
+ LOGE("GLITCH: addr %d has no data\n", addr);
+ return false;
+ }
+
+ u1 val;
+ 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 = *data++;
+ }
+
+ bitIsRef = val & 0x01;
+
+ RegType type = regs[i];
+ regIsRef = isReferenceType(type);
+
+ if (bitIsRef != regIsRef) {
+ LOGE("GLITCH: addr %d reg %d: bit=%d reg=%d(%d)\n",
+ addr, i, bitIsRef, regIsRef, type);
+ return false;
+ }
+ }
+
+ /* print the map as a binary string */
+ if (false) {
+ char outBuf[vdata->method->registersSize +1];
+ for (i = 0; i < vdata->method->registersSize; i++) {
+ if (isReferenceType(regs[i])) {
+ outBuf[i] = '1';
+ } else {
+ outBuf[i] = '0';
+ }
+ }
+ outBuf[i] = '\0';
+ LOGD(" %04d %s\n", addr, outBuf);
+ }
+ }
+
+ return true;
+}
+
+
+/*
+ * ===========================================================================
+ * Just-in-time generation
+ * ===========================================================================
+ */
+
+#if 0 /* incomplete implementation; may be removed entirely in the future */
+
/*
* This is like RegType in the verifier, but simplified. It holds a value
* from the reg type enum, or kRegTypeReference.
@@ -1152,12 +1408,13 @@ sget_1nr_common:
/*
* See comments in analysis/CodeVerify.c re: why some of these are
- * annoying to deal with. In here, "annoying" turns into "impossible",
- * since we make no effort to keep reference type info.
+ * annoying to deal with. It's worse in this implementation, because
+ * we're not keeping any information about the classes held in each
+ * reference register.
*
* Handling most of these would require retaining the field/method
* reference info that we discarded when the instructions were
- * quickened.
+ * quickened. This is feasible but not currently supported.
*/
case OP_EXECUTE_INLINE:
case OP_INVOKE_DIRECT_EMPTY:
@@ -1171,7 +1428,7 @@ sget_1nr_common:
case OP_INVOKE_VIRTUAL_QUICK_RANGE:
case OP_INVOKE_SUPER_QUICK:
case OP_INVOKE_SUPER_QUICK_RANGE:
- dvmAbort(); // can't work
+ dvmAbort(); // not implemented, shouldn't be here
break;
@@ -1431,3 +1688,5 @@ static void updateRegisters(WorkState* pState, int nextInsn,
}
}
+#endif /*#if 0*/
+
diff --git a/vm/analysis/RegisterMap.h b/vm/analysis/RegisterMap.h
index 07071d2d4..2a890e777 100644
--- a/vm/analysis/RegisterMap.h
+++ b/vm/analysis/RegisterMap.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+// ** UNDER CONSTRUCTION **
+
/*
* Declaration of register map data structure and related functions.
*/
@@ -21,30 +23,78 @@
#define _DALVIK_REGISTERMAP
/*
+ * Format enumeration for RegisterMap data area.
+ */
+typedef enum RegisterMapFormat {
+ kFormatUnknown = 0,
+ kFormatCompact8, /* compact layout, 8-bit addresses */
+ kFormatCompact16, /* compact layout, 16-bit addresses */
+ // TODO: compressed stream
+} RegisterMapFormat;
+
+/*
* This is a single variable-size structure. It may be allocated on the
- * heap or mapped out of a DEX file.
+ * heap or mapped out of a (post-dexopt) DEX file.
*/
-typedef struct RegisterMap {
+struct RegisterMap {
/* header */
- char addrWidth; /* bytes per address, 1 or 2 */
- char regWidth; /* bytes per register line, 1+ */
+ u1 format; /* enum RegisterMapFormat */
+ u1 regWidth; /* bytes per register line, 1+ */
+ u2 numEntries; /* number of entries */
- /* char pad0, pad1; */
-
- /* entries start here; 32-bit align guaranteed */
- u4 entries[1];
-} RegisterMap;
+ /* data starts here; no alignment guarantees made */
+ u1 data[1];
+};
/*
- * Generate the register map for a method.
+ * Generate the register map for a previously-verified method.
*
- * Returns a pointer to newly-allocated storage.
+ * Returns a pointer to a newly-allocated RegisterMap.
*/
-RegisterMap* dvmGenerateRegisterMap(const Method* meth);
+//RegisterMap* dvmGenerateRegisterMap(const Method* meth);
/*
- * Release the storage associated with a RegisterMap.
+ * Various bits of data generated by the verifier, wrapped up in a package
+ * for ease of use by the register map generator.
+ */
+typedef struct VerifierData {
+ /*
+ * The method we're working on.
+ */
+ const Method* method;
+
+ /*
+ * Number of instructions in the method.
+ */
+ int insnsSize;
+
+ /*
+ * Number of registers we track for each instruction. This is equal
+ * to the method's declared "registersSize". (Does not include the
+ * pending return value.)
+ */
+ int insnRegCount;
+
+ /*
+ * Instruction widths and flags, one entry per code unit.
+ */
+ InsnFlags* insnFlags;
+
+ /*
+ * Array of SRegType arrays, one entry per code unit. We only need
+ * entries for code units that hold the start of an "interesting"
+ * instruction. For register map generation, we're only interested
+ * in GC points.
+ */
+ RegType** addrRegs;
+} VerifierData;
+
+/*
+ * Generate the register map for a method that has just been verified
+ * (i.e. we're doing this as part of verification).
+ *
+ * Returns a pointer to a newly-allocated RegisterMap, or NULL on failure.
*/
-void dvmFreeRegisterMap(RegisterMap* pMap);
+RegisterMap* dvmGenerateRegisterMapV(VerifierData* vdata);
#endif /*_DALVIK_REGISTERMAP*/
diff --git a/vm/hprof/Hprof.c b/vm/hprof/Hprof.c
index e195aee06..66b46f485 100644
--- a/vm/hprof/Hprof.c
+++ b/vm/hprof/Hprof.c
@@ -13,44 +13,56 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
+/*
+ * Preparation and completion of hprof data generation. The output is
+ * written into two files and then combined. This is necessary because
+ * we generate some of the data (strings and classes) while we dump the
+ * heap, and some analysis tools require that the class and string data
+ * appear first.
+ */
#include "Hprof.h"
+
+#include <string.h>
+#include <unistd.h>
#include <errno.h>
#include <sys/time.h>
#include <time.h>
-#define TWO_FILES 1
+
+#define kHeadSuffix "-hptemp"
hprof_context_t *
-hprofStartup(const char *outputDir)
+hprofStartup(const char *outputFileName)
{
hprof_context_t *ctx;
ctx = malloc(sizeof(*ctx));
if (ctx != NULL) {
+ int len = strlen(outputFileName);
+ char fileName[len + sizeof(kHeadSuffix)];
FILE *fp;
- struct timeval tv;
- /* Construct the output file name.
+ /* Construct the temp file name. This wasn't handed to us by the
+ * application, so we need to be careful about stomping on it.
*/
- int len = strlen(outputDir);
- len += 64; // hprofShutdown assumes that there's some slack
- gettimeofday(&tv, NULL);
- char *fileName = malloc(len);
- if (fileName == NULL) {
- LOGE("hprof: can't malloc %d bytes.\n", len);
+ sprintf(fileName, "%s" kHeadSuffix, outputFileName);
+ if (access(fileName, F_OK) == 0) {
+ LOGE("hprof: temp file %s exists, bailing\n", fileName);
free(ctx);
return NULL;
}
- snprintf(fileName, len, "%s/heap-dump-tm%d-pid%d.hprof",
- outputDir, (int)tv.tv_sec, getpid());
- fileName[len-1] = '\0';
- fp = fopen(fileName, "w");
+ fp = fopen(fileName, "w+");
if (fp == NULL) {
LOGE("hprof: can't open %s: %s.\n", fileName, strerror(errno));
free(ctx);
return NULL;
}
+ if (unlink(fileName) != 0) {
+ LOGW("hprof: WARNING: unable to remove temp file %s\n", fileName);
+ /* keep going */
+ }
LOGI("hprof: dumping VM heap to \"%s\".\n", fileName);
hprofStartup_String();
@@ -59,11 +71,9 @@ hprofStartup(const char *outputDir)
hprofStartup_StackFrame();
hprofStartup_Stack();
#endif
-#if TWO_FILES
- hprofContextInit(ctx, fileName, fp, false);
-#else
- hprofContextInit(ctx, fileName, fp, true);
-#endif
+
+ /* pass in "fp" for the temp file, and the name of the output file */
+ hprofContextInit(ctx, strdup(outputFileName), fp, false);
} else {
LOGE("hprof: can't allocate context.\n");
}
@@ -71,31 +81,59 @@ hprofStartup(const char *outputDir)
return ctx;
}
+/*
+ * Copy the entire contents of "srcFp" to "dstFp".
+ *
+ * Returns "true" on success.
+ */
+static bool
+copyFileToFile(FILE *dstFp, FILE *srcFp)
+{
+ char buf[65536];
+ size_t dataRead, dataWritten;
+
+ while (true) {
+ dataRead = fread(buf, 1, sizeof(buf), srcFp);
+ if (dataRead > 0) {
+ dataWritten = fwrite(buf, 1, dataRead, dstFp);
+ if (dataWritten != dataRead) {
+ LOGE("hprof: failed writing data (%d of %d): %s\n",
+ dataWritten, dataRead, strerror(errno));
+ return false;
+ }
+ } else {
+ if (feof(srcFp))
+ return true;
+ LOGE("hprof: failed reading data (res=%d): %s\n",
+ dataRead, strerror(errno));
+ return false;
+ }
+ }
+}
+
void
hprofShutdown(hprof_context_t *ctx)
{
-#if TWO_FILES
+ FILE *tempFp = ctx->fp;
FILE *fp;
- /* hprofStartup allocated some slack, so the strcat() should be ok.
- */
- char *fileName = strcat(ctx->fileName, "-head");
-
+ /* flush output to the temp file, then prepare the output file */
hprofFlushCurrentRecord(ctx);
- fclose(ctx->fp);
free(ctx->curRec.body);
+ ctx->curRec.body = NULL;
ctx->curRec.allocLen = 0;
+ ctx->fp = NULL;
- LOGI("hprof: dumping heap strings to \"%s\".\n", fileName);
- fp = fopen(fileName, "w");
+ LOGI("hprof: dumping heap strings to \"%s\".\n", ctx->fileName);
+ fp = fopen(ctx->fileName, "w");
if (fp == NULL) {
- LOGE("can't open %s: %s\n", fileName, strerror(errno));
+ LOGE("can't open %s: %s\n", ctx->fileName, strerror(errno));
+ fclose(tempFp);
free(ctx->fileName);
free(ctx);
return;
}
hprofContextInit(ctx, ctx->fileName, fp, true);
-#endif
hprofDumpStrings(ctx);
hprofDumpClasses(ctx);
@@ -122,7 +160,23 @@ hprofShutdown(hprof_context_t *ctx)
hprofShutdown_StackFrame();
#endif
+ /*
+ * Append the contents of the temp file to the output file. The temp
+ * file was removed immediately after being opened, so it will vanish
+ * when we close it.
+ */
+ rewind(tempFp);
+ if (!copyFileToFile(ctx->fp, tempFp)) {
+ LOGW("hprof: file copy failed, hprof data may be incomplete\n");
+ /* finish up anyway */
+ }
+
+ fclose(tempFp);
fclose(ctx->fp);
free(ctx->fileName);
+ free(ctx->curRec.body);
free(ctx);
+
+ /* throw out a log message for the benefit of "runhat" */
+ LOGI("hprof: heap dump completed, temp file removed\n");
}
diff --git a/vm/hprof/Hprof.h b/vm/hprof/Hprof.h
index 6c120f3f6..e0e2d4b6e 100644
--- a/vm/hprof/Hprof.h
+++ b/vm/hprof/Hprof.h
@@ -178,7 +178,7 @@ int hprofDumpHeapObject(hprof_context_t *ctx, const Object *obj);
*/
void hprofContextInit(hprof_context_t *ctx, char *fileName, FILE *fp,
- bool newFile);
+ bool writeHeader);
int hprofFlushRecord(hprof_record_t *rec, FILE *fp);
int hprofFlushCurrentRecord(hprof_context_t *ctx);
@@ -234,7 +234,7 @@ int hprofShutdown_StackFrame(void);
* Hprof.c functions
*/
-hprof_context_t *hprofStartup(const char *outputDir);
+hprof_context_t *hprofStartup(const char *outputFileName);
void hprofShutdown(hprof_context_t *ctx);
/*
@@ -244,7 +244,7 @@ void hprofShutdown(hprof_context_t *ctx);
* the heap implementation; these functions require heap knowledge,
* so they are implemented in Heap.c.
*/
-void hprofDumpHeap();
+void hprofDumpHeap(const char* fileName);
void dvmHeapSetHprofGcScanState(hprof_heap_tag_t state, u4 threadSerialNumber);
#endif // _DALVIK_HPROF_HPROF
diff --git a/vm/hprof/HprofOutput.c b/vm/hprof/HprofOutput.c
index 8571dac06..c6d1cbce8 100644
--- a/vm/hprof/HprofOutput.c
+++ b/vm/hprof/HprofOutput.c
@@ -55,7 +55,8 @@
} while (0)
void
-hprofContextInit(hprof_context_t *ctx, char *fileName, FILE *fp, bool newFile)
+hprofContextInit(hprof_context_t *ctx, char *fileName, FILE *fp,
+ bool writeHeader)
{
memset(ctx, 0, sizeof (*ctx));
ctx->fileName = fileName;
@@ -65,7 +66,7 @@ hprofContextInit(hprof_context_t *ctx, char *fileName, FILE *fp, bool newFile)
ctx->curRec.body = malloc(ctx->curRec.allocLen);
//xxx check for/return an error
- if (newFile) {
+ if (writeHeader) {
char magic[] = HPROF_MAGIC_STRING;
unsigned char buf[4];
struct timeval now;
diff --git a/vm/interp/Interp.c b/vm/interp/Interp.c
index 0287f823d..27b9582ab 100644
--- a/vm/interp/Interp.c
+++ b/vm/interp/Interp.c
@@ -547,7 +547,7 @@ bool dvmInterpHandleFillArrayData(ArrayObject* arrayObj, const u2* arrayData)
width = arrayData[1];
size = arrayData[2] | (((u4)arrayData[3]) << 16);
- if (size != arrayObj->length) {
+ if (size > arrayObj->length) {
dvmThrowException("Ljava/lang/ArrayIndexOutOfBoundsException;", NULL);
return false;
}
diff --git a/vm/mterp/armv5te/OP_FILLED_NEW_ARRAY.S b/vm/mterp/armv5te/OP_FILLED_NEW_ARRAY.S
index 6cc450db0..a9c2d3ef2 100644
--- a/vm/mterp/armv5te/OP_FILLED_NEW_ARRAY.S
+++ b/vm/mterp/armv5te/OP_FILLED_NEW_ARRAY.S
@@ -33,16 +33,18 @@
.L${opcode}_continue:
ldr r3, [r0, #offClassObject_descriptor] @ r3<- arrayClass->descriptor
mov r2, #ALLOC_DONT_TRACK @ r2<- alloc flags
- ldrb r0, [r3, #1] @ r0<- descriptor[1]
+ ldrb r3, [r3, #1] @ r3<- descriptor[1]
.if $isrange
mov r1, r10 @ r1<- AA (length)
.else
mov r1, r10, lsr #4 @ r1<- B (length)
.endif
- cmp r0, #'I' @ array of ints?
+ cmp r3, #'I' @ array of ints?
+ cmpne r3, #'L' @ array of objects?
+ cmpne r3, #'[' @ array of arrays?
mov r9, r1 @ save length in r9
bne .L${opcode}_notimpl @ no, not handled yet
- bl dvmAllocPrimitiveArray @ r0<- call(typeCh, length, flags)
+ bl dvmAllocArrayByClass @ r0<- call(arClass, length, flags)
cmp r0, #0 @ null return?
beq common_exceptionThrown @ alloc failed, handle exception
diff --git a/vm/mterp/armv5te/footer.S b/vm/mterp/armv5te/footer.S
index 70297b60f..3a725ed2b 100644
--- a/vm/mterp/armv5te/footer.S
+++ b/vm/mterp/armv5te/footer.S
@@ -694,7 +694,7 @@ strPrintLong:
.LstrDivideByZero:
.asciz "divide by zero"
.LstrFilledNewArrayNotImpl:
- .asciz "filled-new-array only implemented for 'int'"
+ .asciz "filled-new-array only implemented for objects and 'int'"
.LstrInternalError:
.asciz "Ljava/lang/InternalError;"
.LstrInstantiationError:
diff --git a/vm/mterp/c/gotoTargets.c b/vm/mterp/c/gotoTargets.c
index 1e14a9a6e..f97adf5d7 100644
--- a/vm/mterp/c/gotoTargets.c
+++ b/vm/mterp/c/gotoTargets.c
@@ -12,7 +12,7 @@ GOTO_TARGET(filledNewArray, bool methodCallRange)
{
ClassObject* arrayClass;
ArrayObject* newArray;
- int* contents;
+ u4* contents;
char typeCh;
int i;
u4 arg5;
@@ -64,31 +64,22 @@ GOTO_TARGET(filledNewArray, bool methodCallRange)
dvmThrowException("Ljava/lang/RuntimeError;",
"bad filled array req");
GOTO_exceptionThrown();
- } else if (typeCh == 'L' || typeCh == '[') {
- /* create array of objects or array of arrays */
- /* TODO: need some work in the verifier before we allow this */
- LOGE("fnao not implemented\n");
- dvmThrowException("Ljava/lang/InternalError;",
- "filled-new-array not implemented for reference types");
- GOTO_exceptionThrown();
- } else if (typeCh != 'I') {
+ } else if (typeCh != 'L' && typeCh != '[' && typeCh != 'I') {
/* TODO: requires multiple "fill in" loops with different widths */
- LOGE("non-int not implemented\n");
+ LOGE("non-int primitives not implemented\n");
dvmThrowException("Ljava/lang/InternalError;",
"filled-new-array not implemented for anything but 'int'");
GOTO_exceptionThrown();
}
- assert(strchr("BCIFZ", typeCh) != NULL);
- newArray = dvmAllocPrimitiveArray(arrayClass->descriptor[1], vsrc1,
- ALLOC_DONT_TRACK);
+ newArray = dvmAllocArrayByClass(arrayClass, vsrc1, ALLOC_DONT_TRACK);
if (newArray == NULL)
GOTO_exceptionThrown();
/*
* Fill in the elements. It's legal for vsrc1 to be zero.
*/
- contents = (int*) newArray->contents;
+ contents = (u4*) newArray->contents;
if (methodCallRange) {
for (i = 0; i < vsrc1; i++)
contents[i] = GET_REGISTER(vdst+i);
diff --git a/vm/mterp/out/InterpAsm-armv4.S b/vm/mterp/out/InterpAsm-armv4.S
index 253ff734f..07acd91f3 100644
--- a/vm/mterp/out/InterpAsm-armv4.S
+++ b/vm/mterp/out/InterpAsm-armv4.S
@@ -8056,16 +8056,18 @@ dvmAsmSisterStart:
.LOP_FILLED_NEW_ARRAY_continue:
ldr r3, [r0, #offClassObject_descriptor] @ r3<- arrayClass->descriptor
mov r2, #ALLOC_DONT_TRACK @ r2<- alloc flags
- ldrb r0, [r3, #1] @ r0<- descriptor[1]
+ ldrb r3, [r3, #1] @ r3<- descriptor[1]
.if 0
mov r1, r10 @ r1<- AA (length)
.else
mov r1, r10, lsr #4 @ r1<- B (length)
.endif
- cmp r0, #'I' @ array of ints?
+ cmp r3, #'I' @ array of ints?
+ cmpne r3, #'L' @ array of objects?
+ cmpne r3, #'[' @ array of arrays?
mov r9, r1 @ save length in r9
bne .LOP_FILLED_NEW_ARRAY_notimpl @ no, not handled yet
- bl dvmAllocPrimitiveArray @ r0<- call(typeCh, length, flags)
+ bl dvmAllocArrayByClass @ r0<- call(arClass, length, flags)
cmp r0, #0 @ null return?
beq common_exceptionThrown @ alloc failed, handle exception
@@ -8133,16 +8135,18 @@ dvmAsmSisterStart:
.LOP_FILLED_NEW_ARRAY_RANGE_continue:
ldr r3, [r0, #offClassObject_descriptor] @ r3<- arrayClass->descriptor
mov r2, #ALLOC_DONT_TRACK @ r2<- alloc flags
- ldrb r0, [r3, #1] @ r0<- descriptor[1]
+ ldrb r3, [r3, #1] @ r3<- descriptor[1]
.if 1
mov r1, r10 @ r1<- AA (length)
.else
mov r1, r10, lsr #4 @ r1<- B (length)
.endif
- cmp r0, #'I' @ array of ints?
+ cmp r3, #'I' @ array of ints?
+ cmpne r3, #'L' @ array of objects?
+ cmpne r3, #'[' @ array of arrays?
mov r9, r1 @ save length in r9
bne .LOP_FILLED_NEW_ARRAY_RANGE_notimpl @ no, not handled yet
- bl dvmAllocPrimitiveArray @ r0<- call(typeCh, length, flags)
+ bl dvmAllocArrayByClass @ r0<- call(arClass, length, flags)
cmp r0, #0 @ null return?
beq common_exceptionThrown @ alloc failed, handle exception
@@ -9978,7 +9982,7 @@ strPrintLong:
.LstrDivideByZero:
.asciz "divide by zero"
.LstrFilledNewArrayNotImpl:
- .asciz "filled-new-array only implemented for 'int'"
+ .asciz "filled-new-array only implemented for objects and 'int'"
.LstrInternalError:
.asciz "Ljava/lang/InternalError;"
.LstrInstantiationError:
diff --git a/vm/mterp/out/InterpAsm-armv5te.S b/vm/mterp/out/InterpAsm-armv5te.S
index 344b90022..765cdb264 100644
--- a/vm/mterp/out/InterpAsm-armv5te.S
+++ b/vm/mterp/out/InterpAsm-armv5te.S
@@ -8054,16 +8054,18 @@ dvmAsmSisterStart:
.LOP_FILLED_NEW_ARRAY_continue:
ldr r3, [r0, #offClassObject_descriptor] @ r3<- arrayClass->descriptor
mov r2, #ALLOC_DONT_TRACK @ r2<- alloc flags
- ldrb r0, [r3, #1] @ r0<- descriptor[1]
+ ldrb r3, [r3, #1] @ r3<- descriptor[1]
.if 0
mov r1, r10 @ r1<- AA (length)
.else
mov r1, r10, lsr #4 @ r1<- B (length)
.endif
- cmp r0, #'I' @ array of ints?
+ cmp r3, #'I' @ array of ints?
+ cmpne r3, #'L' @ array of objects?
+ cmpne r3, #'[' @ array of arrays?
mov r9, r1 @ save length in r9
bne .LOP_FILLED_NEW_ARRAY_notimpl @ no, not handled yet
- bl dvmAllocPrimitiveArray @ r0<- call(typeCh, length, flags)
+ bl dvmAllocArrayByClass @ r0<- call(arClass, length, flags)
cmp r0, #0 @ null return?
beq common_exceptionThrown @ alloc failed, handle exception
@@ -8131,16 +8133,18 @@ dvmAsmSisterStart:
.LOP_FILLED_NEW_ARRAY_RANGE_continue:
ldr r3, [r0, #offClassObject_descriptor] @ r3<- arrayClass->descriptor
mov r2, #ALLOC_DONT_TRACK @ r2<- alloc flags
- ldrb r0, [r3, #1] @ r0<- descriptor[1]
+ ldrb r3, [r3, #1] @ r3<- descriptor[1]
.if 1
mov r1, r10 @ r1<- AA (length)
.else
mov r1, r10, lsr #4 @ r1<- B (length)
.endif
- cmp r0, #'I' @ array of ints?
+ cmp r3, #'I' @ array of ints?
+ cmpne r3, #'L' @ array of objects?
+ cmpne r3, #'[' @ array of arrays?
mov r9, r1 @ save length in r9
bne .LOP_FILLED_NEW_ARRAY_RANGE_notimpl @ no, not handled yet
- bl dvmAllocPrimitiveArray @ r0<- call(typeCh, length, flags)
+ bl dvmAllocArrayByClass @ r0<- call(arClass, length, flags)
cmp r0, #0 @ null return?
beq common_exceptionThrown @ alloc failed, handle exception
@@ -9972,7 +9976,7 @@ strPrintLong:
.LstrDivideByZero:
.asciz "divide by zero"
.LstrFilledNewArrayNotImpl:
- .asciz "filled-new-array only implemented for 'int'"
+ .asciz "filled-new-array only implemented for objects and 'int'"
.LstrInternalError:
.asciz "Ljava/lang/InternalError;"
.LstrInstantiationError:
diff --git a/vm/mterp/out/InterpAsm-x86.S b/vm/mterp/out/InterpAsm-x86.S
index 857168239..749cdde77 100644
--- a/vm/mterp/out/InterpAsm-x86.S
+++ b/vm/mterp/out/InterpAsm-x86.S
@@ -1,3 +1,4 @@
+
/*
* This file was generated automatically by gen-mterp.py for 'x86'.
*
@@ -6543,15 +6544,20 @@ dvmAsmSisterStart:
movl offClassObject_descriptor(%eax),%ecx # ecx<- arrayClass->descriptor
movl $ALLOC_DONT_TRACK,OUT_ARG2(%esp) # arg2<- flags
movzbl 1(%ecx),%ecx # ecx<- descriptor[1]
- movl %ecx,OUT_ARG0(%esp) # arg0<- typeCh
+ movl %eax,OUT_ARG0(%esp) # arg0<- arrayClass
cmpb $'I',%cl # supported?
+ je 1f
+ cmpb $'L',%cl
+ je 1f
+ cmpb $'[',%cl
jne .LOP_FILLED_NEW_ARRAY_notimpl # no, not handled yet
+1:
.if (!0)
SPILL_TMP(rINST_FULL) # save copy, need "B" later
sarl $4,rINST_FULL
.endif
movl rINST_FULL,OUT_ARG1(%esp) # arg1<- A or AA (length)
- call dvmAllocPrimitiveArray # eax<- call(typeCh, length, flags)
+ call dvmAllocArrayByClass # eax<- call(typeCh, length, flags)
UNSPILL(rPC)
GET_GLUE(%ecx)
testl %eax,%eax # alloc successful?
@@ -6643,15 +6649,20 @@ dvmAsmSisterStart:
movl offClassObject_descriptor(%eax),%ecx # ecx<- arrayClass->descriptor
movl $ALLOC_DONT_TRACK,OUT_ARG2(%esp) # arg2<- flags
movzbl 1(%ecx),%ecx # ecx<- descriptor[1]
- movl %ecx,OUT_ARG0(%esp) # arg0<- typeCh
+ movl %eax,OUT_ARG0(%esp) # arg0<- arrayClass
cmpb $'I',%cl # supported?
+ je 1f
+ cmpb $'L',%cl
+ je 1f
+ cmpb $'[',%cl
jne .LOP_FILLED_NEW_ARRAY_RANGE_notimpl # no, not handled yet
+1:
.if (!1)
SPILL_TMP(rINST_FULL) # save copy, need "B" later
sarl $4,rINST_FULL
.endif
movl rINST_FULL,OUT_ARG1(%esp) # arg1<- A or AA (length)
- call dvmAllocPrimitiveArray # eax<- call(typeCh, length, flags)
+ call dvmAllocArrayByClass # eax<- call(typeCh, length, flags)
UNSPILL(rPC)
GET_GLUE(%ecx)
testl %eax,%eax # alloc successful?
diff --git a/vm/mterp/out/InterpC-allstubs.c b/vm/mterp/out/InterpC-allstubs.c
index 02592bdff..8d253e0e9 100644
--- a/vm/mterp/out/InterpC-allstubs.c
+++ b/vm/mterp/out/InterpC-allstubs.c
@@ -3037,7 +3037,7 @@ GOTO_TARGET(filledNewArray, bool methodCallRange)
{
ClassObject* arrayClass;
ArrayObject* newArray;
- int* contents;
+ u4* contents;
char typeCh;
int i;
u4 arg5;
@@ -3089,31 +3089,22 @@ GOTO_TARGET(filledNewArray, bool methodCallRange)
dvmThrowException("Ljava/lang/RuntimeError;",
"bad filled array req");
GOTO_exceptionThrown();
- } else if (typeCh == 'L' || typeCh == '[') {
- /* create array of objects or array of arrays */
- /* TODO: need some work in the verifier before we allow this */
- LOGE("fnao not implemented\n");
- dvmThrowException("Ljava/lang/InternalError;",
- "filled-new-array not implemented for reference types");
- GOTO_exceptionThrown();
- } else if (typeCh != 'I') {
+ } else if (typeCh != 'L' && typeCh != '[' && typeCh != 'I') {
/* TODO: requires multiple "fill in" loops with different widths */
- LOGE("non-int not implemented\n");
+ LOGE("non-int primitives not implemented\n");
dvmThrowException("Ljava/lang/InternalError;",
"filled-new-array not implemented for anything but 'int'");
GOTO_exceptionThrown();
}
- assert(strchr("BCIFZ", typeCh) != NULL);
- newArray = dvmAllocPrimitiveArray(arrayClass->descriptor[1], vsrc1,
- ALLOC_DONT_TRACK);
+ newArray = dvmAllocArrayByClass(arrayClass, vsrc1, ALLOC_DONT_TRACK);
if (newArray == NULL)
GOTO_exceptionThrown();
/*
* Fill in the elements. It's legal for vsrc1 to be zero.
*/
- contents = (int*) newArray->contents;
+ contents = (u4*) newArray->contents;
if (methodCallRange) {
for (i = 0; i < vsrc1; i++)
contents[i] = GET_REGISTER(vdst+i);
diff --git a/vm/mterp/out/InterpC-portdbg.c b/vm/mterp/out/InterpC-portdbg.c
index cc23d33f3..503425db6 100644
--- a/vm/mterp/out/InterpC-portdbg.c
+++ b/vm/mterp/out/InterpC-portdbg.c
@@ -3298,7 +3298,7 @@ GOTO_TARGET(filledNewArray, bool methodCallRange)
{
ClassObject* arrayClass;
ArrayObject* newArray;
- int* contents;
+ u4* contents;
char typeCh;
int i;
u4 arg5;
@@ -3350,31 +3350,22 @@ GOTO_TARGET(filledNewArray, bool methodCallRange)
dvmThrowException("Ljava/lang/RuntimeError;",
"bad filled array req");
GOTO_exceptionThrown();
- } else if (typeCh == 'L' || typeCh == '[') {
- /* create array of objects or array of arrays */
- /* TODO: need some work in the verifier before we allow this */
- LOGE("fnao not implemented\n");
- dvmThrowException("Ljava/lang/InternalError;",
- "filled-new-array not implemented for reference types");
- GOTO_exceptionThrown();
- } else if (typeCh != 'I') {
+ } else if (typeCh != 'L' && typeCh != '[' && typeCh != 'I') {
/* TODO: requires multiple "fill in" loops with different widths */
- LOGE("non-int not implemented\n");
+ LOGE("non-int primitives not implemented\n");
dvmThrowException("Ljava/lang/InternalError;",
"filled-new-array not implemented for anything but 'int'");
GOTO_exceptionThrown();
}
- assert(strchr("BCIFZ", typeCh) != NULL);
- newArray = dvmAllocPrimitiveArray(arrayClass->descriptor[1], vsrc1,
- ALLOC_DONT_TRACK);
+ newArray = dvmAllocArrayByClass(arrayClass, vsrc1, ALLOC_DONT_TRACK);
if (newArray == NULL)
GOTO_exceptionThrown();
/*
* Fill in the elements. It's legal for vsrc1 to be zero.
*/
- contents = (int*) newArray->contents;
+ contents = (u4*) newArray->contents;
if (methodCallRange) {
for (i = 0; i < vsrc1; i++)
contents[i] = GET_REGISTER(vdst+i);
diff --git a/vm/mterp/out/InterpC-portstd.c b/vm/mterp/out/InterpC-portstd.c
index 78722361b..7292c0b19 100644
--- a/vm/mterp/out/InterpC-portstd.c
+++ b/vm/mterp/out/InterpC-portstd.c
@@ -3018,7 +3018,7 @@ GOTO_TARGET(filledNewArray, bool methodCallRange)
{
ClassObject* arrayClass;
ArrayObject* newArray;
- int* contents;
+ u4* contents;
char typeCh;
int i;
u4 arg5;
@@ -3070,31 +3070,22 @@ GOTO_TARGET(filledNewArray, bool methodCallRange)
dvmThrowException("Ljava/lang/RuntimeError;",
"bad filled array req");
GOTO_exceptionThrown();
- } else if (typeCh == 'L' || typeCh == '[') {
- /* create array of objects or array of arrays */
- /* TODO: need some work in the verifier before we allow this */
- LOGE("fnao not implemented\n");
- dvmThrowException("Ljava/lang/InternalError;",
- "filled-new-array not implemented for reference types");
- GOTO_exceptionThrown();
- } else if (typeCh != 'I') {
+ } else if (typeCh != 'L' && typeCh != '[' && typeCh != 'I') {
/* TODO: requires multiple "fill in" loops with different widths */
- LOGE("non-int not implemented\n");
+ LOGE("non-int primitives not implemented\n");
dvmThrowException("Ljava/lang/InternalError;",
"filled-new-array not implemented for anything but 'int'");
GOTO_exceptionThrown();
}
- assert(strchr("BCIFZ", typeCh) != NULL);
- newArray = dvmAllocPrimitiveArray(arrayClass->descriptor[1], vsrc1,
- ALLOC_DONT_TRACK);
+ newArray = dvmAllocArrayByClass(arrayClass, vsrc1, ALLOC_DONT_TRACK);
if (newArray == NULL)
GOTO_exceptionThrown();
/*
* Fill in the elements. It's legal for vsrc1 to be zero.
*/
- contents = (int*) newArray->contents;
+ contents = (u4*) newArray->contents;
if (methodCallRange) {
for (i = 0; i < vsrc1; i++)
contents[i] = GET_REGISTER(vdst+i);
diff --git a/vm/mterp/out/InterpC-x86.c b/vm/mterp/out/InterpC-x86.c
index 313499325..fa45730a3 100644
--- a/vm/mterp/out/InterpC-x86.c
+++ b/vm/mterp/out/InterpC-x86.c
@@ -1187,7 +1187,7 @@ GOTO_TARGET(filledNewArray, bool methodCallRange)
{
ClassObject* arrayClass;
ArrayObject* newArray;
- int* contents;
+ u4* contents;
char typeCh;
int i;
u4 arg5;
@@ -1239,31 +1239,22 @@ GOTO_TARGET(filledNewArray, bool methodCallRange)
dvmThrowException("Ljava/lang/RuntimeError;",
"bad filled array req");
GOTO_exceptionThrown();
- } else if (typeCh == 'L' || typeCh == '[') {
- /* create array of objects or array of arrays */
- /* TODO: need some work in the verifier before we allow this */
- LOGE("fnao not implemented\n");
- dvmThrowException("Ljava/lang/InternalError;",
- "filled-new-array not implemented for reference types");
- GOTO_exceptionThrown();
- } else if (typeCh != 'I') {
+ } else if (typeCh != 'L' && typeCh != '[' && typeCh != 'I') {
/* TODO: requires multiple "fill in" loops with different widths */
- LOGE("non-int not implemented\n");
+ LOGE("non-int primitives not implemented\n");
dvmThrowException("Ljava/lang/InternalError;",
"filled-new-array not implemented for anything but 'int'");
GOTO_exceptionThrown();
}
- assert(strchr("BCIFZ", typeCh) != NULL);
- newArray = dvmAllocPrimitiveArray(arrayClass->descriptor[1], vsrc1,
- ALLOC_DONT_TRACK);
+ newArray = dvmAllocArrayByClass(arrayClass, vsrc1, ALLOC_DONT_TRACK);
if (newArray == NULL)
GOTO_exceptionThrown();
/*
* Fill in the elements. It's legal for vsrc1 to be zero.
*/
- contents = (int*) newArray->contents;
+ contents = (u4*) newArray->contents;
if (methodCallRange) {
for (i = 0; i < vsrc1; i++)
contents[i] = GET_REGISTER(vdst+i);
diff --git a/vm/mterp/x86/OP_FILLED_NEW_ARRAY.S b/vm/mterp/x86/OP_FILLED_NEW_ARRAY.S
index 85b468d78..aa247a83d 100644
--- a/vm/mterp/x86/OP_FILLED_NEW_ARRAY.S
+++ b/vm/mterp/x86/OP_FILLED_NEW_ARRAY.S
@@ -47,15 +47,20 @@
movl offClassObject_descriptor(%eax),%ecx # ecx<- arrayClass->descriptor
movl $$ALLOC_DONT_TRACK,OUT_ARG2(%esp) # arg2<- flags
movzbl 1(%ecx),%ecx # ecx<- descriptor[1]
- movl %ecx,OUT_ARG0(%esp) # arg0<- typeCh
+ movl %eax,OUT_ARG0(%esp) # arg0<- arrayClass
cmpb $$'I',%cl # supported?
+ je 1f
+ cmpb $$'L',%cl
+ je 1f
+ cmpb $$'[',%cl
jne .L${opcode}_notimpl # no, not handled yet
+1:
.if (!$isrange)
SPILL_TMP(rINST_FULL) # save copy, need "B" later
sarl $$4,rINST_FULL
.endif
movl rINST_FULL,OUT_ARG1(%esp) # arg1<- A or AA (length)
- call dvmAllocPrimitiveArray # eax<- call(typeCh, length, flags)
+ call dvmAllocArrayByClass # eax<- call(arrayClass, length, flags)
UNSPILL(rPC)
GET_GLUE(%ecx)
testl %eax,%eax # alloc successful?
diff --git a/vm/native/dalvik_system_VMDebug.c b/vm/native/dalvik_system_VMDebug.c
index 990494e18..9eccb76d1 100644
--- a/vm/native/dalvik_system_VMDebug.c
+++ b/vm/native/dalvik_system_VMDebug.c
@@ -400,10 +400,10 @@ static void Dalvik_dalvik_system_VMDebug_startInstructionCounting(const u4* args
{
#if defined(WITH_PROFILER)
dvmStartInstructionCounting();
- RETURN_VOID();
#else
dvmThrowException("Ljava/lang/UnsupportedOperationException;", NULL);
#endif
+ RETURN_VOID();
}
/*
@@ -414,10 +414,10 @@ static void Dalvik_dalvik_system_VMDebug_stopInstructionCounting(const u4* args,
{
#if defined(WITH_PROFILER)
dvmStopInstructionCounting();
- RETURN_VOID();
#else
dvmThrowException("Ljava/lang/UnsupportedOperationException;", NULL);
#endif
+ RETURN_VOID();
}
/*
@@ -440,11 +440,10 @@ static void Dalvik_dalvik_system_VMDebug_getInstructionCount(const u4* args,
sched_yield();
memcpy(storage, gDvm.executedInstrCounts,
kNumDalvikInstructions * sizeof(int));
-
- RETURN_VOID();
#else
dvmThrowException("Ljava/lang/UnsupportedOperationException;", NULL);
#endif
+ RETURN_VOID();
}
/*
@@ -458,10 +457,10 @@ static void Dalvik_dalvik_system_VMDebug_resetInstructionCount(const u4* args,
#if defined(WITH_PROFILER)
sched_yield();
memset(gDvm.executedInstrCounts, 0, kNumDalvikInstructions * sizeof(int));
- RETURN_VOID();
#else
dvmThrowException("Ljava/lang/UnsupportedOperationException;", NULL);
#endif
+ RETURN_VOID();
}
/*
@@ -501,10 +500,10 @@ static void Dalvik_dalvik_system_VMDebug_getLoadedClassCount(const u4* args,
* or -1 if the feature isn't supported.
*/
static void Dalvik_dalvik_system_VMDebug_threadCpuTimeNanos(const u4* args,
- JValue* pResult)
+ JValue* pResult)
{
jlong result;
-
+
#ifdef HAVE_POSIX_CLOCKS
struct timespec now;
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &now);
@@ -516,49 +515,85 @@ static void Dalvik_dalvik_system_VMDebug_threadCpuTimeNanos(const u4* args,
RETURN_LONG(result);
}
+/*
+ * static void dumpHprofData(String fileName)
+ *
+ * Cause "hprof" data to be dumped. We can throw an IOException if an
+ * error occurs during file handling.
+ */
+static void Dalvik_dalvik_system_VMDebug_dumpHprofData(const u4* args,
+ JValue* pResult)
+{
+#ifdef WITH_HPROF
+ StringObject* fileNameStr = (StringObject*) args[0];
+ char* fileName;
+
+ if (fileNameStr == NULL) {
+ dvmThrowException("Ljava/lang/NullPointerException;", NULL);
+ RETURN_VOID();
+ }
+
+ fileName = dvmCreateCstrFromString(fileNameStr);
+ if (fileName == NULL) {
+ /* unexpected -- malloc failure? */
+ dvmThrowException("Ljava/lang/RuntimeException;", "malloc failure?");
+ RETURN_VOID();
+ }
+
+ hprofDumpHeap(fileName);
+ free(fileName);
+#else
+ dvmThrowException("Ljava/lang/UnsupportedOperationException;", NULL);
+#endif
+
+ RETURN_VOID();
+}
+
const DalvikNativeMethod dvm_dalvik_system_VMDebug[] = {
- { "getAllocCount", "(I)I",
+ { "getAllocCount", "(I)I",
Dalvik_dalvik_system_VMDebug_getAllocCount },
- { "resetAllocCount", "(I)V",
+ { "resetAllocCount", "(I)V",
Dalvik_dalvik_system_VMDebug_resetAllocCount },
//{ "print", "(Ljava/lang/String;)V",
// Dalvik_dalvik_system_VMDebug_print },
- { "startAllocCounting", "()V",
+ { "startAllocCounting", "()V",
Dalvik_dalvik_system_VMDebug_startAllocCounting },
- { "stopAllocCounting", "()V",
+ { "stopAllocCounting", "()V",
Dalvik_dalvik_system_VMDebug_stopAllocCounting },
- { "startMethodTracing", "(Ljava/lang/String;II)V",
+ { "startMethodTracing", "(Ljava/lang/String;II)V",
Dalvik_dalvik_system_VMDebug_startMethodTracing },
- { "stopMethodTracing", "()V",
+ { "stopMethodTracing", "()V",
Dalvik_dalvik_system_VMDebug_stopMethodTracing },
- { "startEmulatorTracing", "()V",
+ { "startEmulatorTracing", "()V",
Dalvik_dalvik_system_VMDebug_startEmulatorTracing },
- { "stopEmulatorTracing", "()V",
+ { "stopEmulatorTracing", "()V",
Dalvik_dalvik_system_VMDebug_stopEmulatorTracing },
- { "setAllocationLimit", "(I)I",
+ { "setAllocationLimit", "(I)I",
Dalvik_dalvik_system_VMDebug_setAllocationLimit },
- { "setGlobalAllocationLimit", "(I)I",
+ { "setGlobalAllocationLimit", "(I)I",
Dalvik_dalvik_system_VMDebug_setGlobalAllocationLimit },
- { "startInstructionCounting", "()V",
+ { "startInstructionCounting", "()V",
Dalvik_dalvik_system_VMDebug_startInstructionCounting },
- { "stopInstructionCounting", "()V",
+ { "stopInstructionCounting", "()V",
Dalvik_dalvik_system_VMDebug_stopInstructionCounting },
- { "resetInstructionCount", "()V",
+ { "resetInstructionCount", "()V",
Dalvik_dalvik_system_VMDebug_resetInstructionCount },
- { "getInstructionCount", "([I)V",
+ { "getInstructionCount", "([I)V",
Dalvik_dalvik_system_VMDebug_getInstructionCount },
- { "isDebuggerConnected", "()Z",
+ { "isDebuggerConnected", "()Z",
Dalvik_dalvik_system_VMDebug_isDebuggerConnected },
- { "isDebuggingEnabled", "()Z",
+ { "isDebuggingEnabled", "()Z",
Dalvik_dalvik_system_VMDebug_isDebuggingEnabled },
- { "lastDebuggerActivity", "()J",
+ { "lastDebuggerActivity", "()J",
Dalvik_dalvik_system_VMDebug_lastDebuggerActivity },
- { "printLoadedClasses", "(I)V",
+ { "printLoadedClasses", "(I)V",
Dalvik_dalvik_system_VMDebug_printLoadedClasses },
- { "getLoadedClassCount", "()I",
+ { "getLoadedClassCount", "()I",
Dalvik_dalvik_system_VMDebug_getLoadedClassCount },
- { "threadCpuTimeNanos", "()J",
+ { "threadCpuTimeNanos", "()J",
Dalvik_dalvik_system_VMDebug_threadCpuTimeNanos },
+ { "dumpHprofData", "(Ljava/lang/String;)V",
+ Dalvik_dalvik_system_VMDebug_dumpHprofData },
{ NULL, NULL, NULL },
};
diff --git a/vm/oo/Class.c b/vm/oo/Class.c
index 0d8114b5b..151a9e381 100644
--- a/vm/oo/Class.c
+++ b/vm/oo/Class.c
@@ -1909,6 +1909,8 @@ static void freeMethodInnards(Method* meth)
free(meth->lines);
free(meth->locals);
#else
+ // TODO: call dvmFreeRegisterMap() if meth->registerMap was allocated
+ // on the system heap
UNUSED_PARAMETER(meth);
#endif
}
@@ -4255,6 +4257,32 @@ void dvmSetNativeFunc(const Method* method, DalvikBridgeFunc func,
}
/*
+ * Add a RegisterMap to a Method. This is done when we verify the class
+ * and compute the register maps at class initialization time, which means
+ * that "pMap" is on the heap and should be freed when the Method is
+ * discarded.
+ */
+void dvmSetRegisterMap(Method* method, const RegisterMap* pMap)
+{
+ ClassObject* clazz = method->clazz;
+
+ if (method->registerMap != NULL) {
+ LOGW("WARNING: registerMap already set for %s.%s\n",
+ method->clazz->descriptor, method->name);
+ /* keep going */
+ }
+
+ /* might be virtual or direct */
+ dvmLinearReadWrite(clazz->classLoader, clazz->virtualMethods);
+ dvmLinearReadWrite(clazz->classLoader, clazz->directMethods);
+
+ method->registerMap = pMap;
+
+ dvmLinearReadOnly(clazz->classLoader, clazz->virtualMethods);
+ dvmLinearReadOnly(clazz->classLoader, clazz->directMethods);
+}
+
+/*
* dvmHashForeach callback. A nonzero return value causes foreach to
* bail out.
*/
diff --git a/vm/oo/Class.h b/vm/oo/Class.h
index 67dbc1c77..349df3fda 100644
--- a/vm/oo/Class.h
+++ b/vm/oo/Class.h
@@ -144,6 +144,11 @@ bool dvmLoaderInInitiatingList(const ClassObject* clazz, const Object* loader);
void dvmSetNativeFunc(const Method* method, DalvikBridgeFunc func,
const u2* insns);
+/*
+ * Set the method's "registerMap" field.
+ */
+void dvmSetRegisterMap(Method* method, const RegisterMap* pMap);
+
/* during DEX optimizing, add an extra DEX to the bootstrap class path */
INLINE void dvmSetBootPathExtraDex(DvmDex* pDvmDex);
diff --git a/vm/oo/Object.h b/vm/oo/Object.h
index 71d804d83..7ef8dac6f 100644
--- a/vm/oo/Object.h
+++ b/vm/oo/Object.h
@@ -33,6 +33,7 @@ struct LineNumEntry;
struct StaticField;
struct InstField;
struct Field;
+struct RegisterMap;
typedef struct DataObject DataObject;
typedef struct ClassObject ClassObject;
typedef struct StringObject StringObject;
@@ -43,6 +44,7 @@ typedef struct LineNumEntry LineNumEntry;
typedef struct StaticField StaticField;
typedef struct InstField InstField;
typedef struct Field Field;
+typedef struct RegisterMap RegisterMap;
/*
* Native function pointer type.
@@ -461,6 +463,13 @@ struct Method {
*/
DalvikBridgeFunc nativeFunc;
+ /*
+ * Register map data, if available. This will point into the DEX file
+ * if the data was computed during pre-verification, or into the
+ * linear alloc area if not.
+ */
+ const RegisterMap* registerMap;
+
#ifdef WITH_PROFILER
bool inProfile;
#endif
diff --git a/vm/oo/Resolve.c b/vm/oo/Resolve.c
index 04c96db20..52eeee0b8 100644
--- a/vm/oo/Resolve.c
+++ b/vm/oo/Resolve.c
@@ -64,6 +64,7 @@ ClassObject* dvmResolveClass(const ClassObject* referrer, u4 classIdx,
{
DvmDex* pDvmDex = referrer->pDvmDex;
ClassObject* resClass;
+ const char* className;
/*
* Check the table first -- this gets called from the other "resolve"
@@ -85,9 +86,13 @@ ClassObject* dvmResolveClass(const ClassObject* referrer, u4 classIdx,
*
* If this is an array class, we'll generate it here.
*/
- resClass = dvmFindClassNoInit(
- dexStringByTypeIdx(pDvmDex->pDexFile, classIdx),
- referrer->classLoader);
+ className = dexStringByTypeIdx(pDvmDex->pDexFile, classIdx);
+ if (className[0] != '\0' && className[1] == '\0') {
+ /* primitive type */
+ resClass = dvmFindPrimitiveClass(className[0]);
+ } else {
+ resClass = dvmFindClassNoInit(className, referrer->classLoader);
+ }
if (resClass != NULL) {
/*
diff --git a/vm/reflect/Annotation.c b/vm/reflect/Annotation.c
index 5b2afd937..c07c602ea 100644
--- a/vm/reflect/Annotation.c
+++ b/vm/reflect/Annotation.c
@@ -484,18 +484,6 @@ static bool processAnnotationValue(const ClassObject* clazz,
elemObj = (Object*) dvmResolveClass(clazz, idx, true);
setObject = true;
if (elemObj == NULL) {
- /* see if it's a primitive type - not common */
- dvmClearException(self);
- const char* className =
- dexStringByTypeIdx(clazz->pDvmDex->pDexFile, idx);
-
- if (className != NULL &&
- className[0] != '\0' && className[1] == '\0')
- {
- elemObj = (Object*) dvmFindPrimitiveClass(className[0]);
- }
- }
- if (elemObj == NULL) {
/* we're expected to throw a TypeNotPresentException here */
DexFile* pDexFile = clazz->pDvmDex->pDexFile;
const char* desc = dexStringByTypeIdx(pDexFile, idx);