diff options
Diffstat (limited to 'vm/interp/Jit.c')
-rw-r--r-- | vm/interp/Jit.c | 567 |
1 files changed, 567 insertions, 0 deletions
diff --git a/vm/interp/Jit.c b/vm/interp/Jit.c new file mode 100644 index 000000000..e23361b4a --- /dev/null +++ b/vm/interp/Jit.c @@ -0,0 +1,567 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifdef WITH_JIT + +/* + * Target independent portion of Android's Jit + */ + +#include "Dalvik.h" +#include "Jit.h" + + +#include "dexdump/OpCodeNames.h" +#include <unistd.h> +#include <pthread.h> +#include <sys/time.h> +#include <signal.h> +#include "compiler/Compiler.h" +#include <errno.h> + +/* + * Reset profile counts. Note that we could easily lose + * one or more of these write because of threading. Because these + * counts are considered hints, absolute correctness is not a + * problem and the cost of synchronizing would be prohibitive. + * NOTE: Experimental - 5/21/09. Keep rough track of the last + * time the counts were reset to allow trace builder to ignore + * stale thresholds. This is just a hint, and the only penalty + * for getting it wrong is a slight performance hit (far less than + * the cost of synchronization). + */ +static u8 lastProfileResetTimeUsec; +static void resetProfileCounts() { + int i; + unsigned char *pJitProfTable = gDvmJit.pProfTable; + lastProfileResetTimeUsec = dvmGetRelativeTimeUsec(); + if (pJitProfTable != NULL) { + for (i=0; i < JIT_PROF_SIZE; i++) { + pJitProfTable[i] = gDvmJit.threshold; + } + } +} + +int dvmJitStartup(void) +{ + unsigned int i; + bool res = true; /* Assume success */ + + // Create the compiler thread and setup miscellaneous chores */ + res &= dvmCompilerStartup(); + + dvmInitMutex(&gDvmJit.tableLock); + if (res && gDvm.executionMode == kExecutionModeJit) { + struct JitEntry *pJitTable = NULL; + int tableSize = sizeof(*pJitTable) * gDvmJit.maxTableEntries; + unsigned char *pJitProfTable = NULL; + dvmLockMutex(&gDvmJit.tableLock); + assert(sizeof(*pJitTable) == 12); + pJitTable = (struct JitEntry*)malloc(tableSize); + if (!pJitTable) { + LOGE("jit table allocation failed\n"); + res = false; + goto done; + } + memset(pJitTable,0,tableSize); + /* + * NOTE: the profile table must only be allocated once, globally. + * Profiling is turned on and off by nulling out gDvm.pJitProfTable + * and then restoring its original value. However, this action + * is not syncronized for speed so threads may continue to hold + * and update the profile table after profiling has been turned + * off by null'ng the global pointer. Be aware. + */ + pJitProfTable = (unsigned char *)malloc(JIT_PROF_SIZE); + if (!pJitProfTable) { + LOGE("jit prof table allocation failed\n"); + res = false; + goto done; + } + memset(pJitProfTable,0,JIT_PROF_SIZE); + for (i=0; i < gDvmJit.maxTableEntries; i++) { + pJitTable[i].chain = gDvmJit.maxTableEntries; + } + /* Is chain field wide enough for termination pattern? */ + assert(pJitTable[0].chain == gDvm.maxJitTableEntries); + resetProfileCounts(); + +done: + gDvmJit.pJitEntryTable = pJitTable; + gDvmJit.pProfTableCopy = gDvmJit.pProfTable = pJitProfTable; + dvmUnlockMutex(&gDvmJit.tableLock); + } + return res; +} + +/* + * If one of our fixed tables or the translation buffer fills up, + * call this routine to avoid wasting cycles on future translation requests. + */ +void dvmJitStopTranslationRequests() +{ + /* + * Note 1: This won't necessarily stop all translation requests, and + * operates on a delayed mechanism. Running threads look to the copy + * of this value in their private InterpState structures and won't see + * this change until it is refreshed (which happens on interpreter + * entry). + * Note 2: This is a one-shot memory leak on this table. Because this is a + * permanent off switch for Jit profiling, it is a one-time leak of 1K + * bytes, and no further attempt will be made to re-allocate it. Can't + * free it because some thread may be holding a reference. + */ + gDvmJit.pProfTable = gDvmJit.pProfTableCopy = NULL; +} + +#if defined(EXIT_STATS) +/* Convenience function to increment counter from assembly code */ +void dvmBumpNoChain() +{ + gDvm.jitNoChainExit++; +} + +/* Convenience function to increment counter from assembly code */ +void dvmBumpNormal() +{ + gDvm.jitNormalExit++; +} + +/* Convenience function to increment counter from assembly code */ +void dvmBumpPunt(int from) +{ + gDvm.jitPuntExit++; +} +#endif + +/* Dumps debugging & tuning stats to the log */ +void dvmJitStats() +{ + int i; + int hit; + int not_hit; + int chains; + if (gDvmJit.pJitEntryTable) { + for (i=0, chains=hit=not_hit=0; + i < (int) gDvmJit.maxTableEntries; + i++) { + if (gDvmJit.pJitEntryTable[i].dPC != 0) + hit++; + else + not_hit++; + if (gDvmJit.pJitEntryTable[i].chain != gDvmJit.maxTableEntries) + chains++; + } + LOGD( + "JIT: %d traces, %d slots, %d chains, %d maxQ, %d thresh, %s", + hit, not_hit + hit, chains, gDvmJit.compilerMaxQueued, + gDvmJit.threshold, gDvmJit.blockingMode ? "Blocking" : "Non-blocking"); +#if defined(EXIT_STATS) + LOGD( + "JIT: Lookups: %d hits, %d misses; %d NoChain, %d normal, %d punt", + gDvmJit.addrLookupsFound, gDvmJit.addrLookupsNotFound, + gDvmJit.noChainExit, gDvmJit.normalExit, gDvmJit.puntExit); +#endif + LOGD("JIT: %d Translation chains", gDvmJit.translationChains); +#if defined(INVOKE_STATS) + LOGD("JIT: Invoke: %d noOpt, %d chainable, %d return", + gDvmJit.invokeNoOpt, gDvmJit.invokeChain, gDvmJit.returnOp); +#endif + } +} + +/* + * Final JIT shutdown. Only do this once, and do not attempt to restart + * the JIT later. + */ +void dvmJitShutdown(void) +{ + /* Shutdown the compiler thread */ + dvmCompilerShutdown(); + + dvmCompilerDumpStats(); + + dvmDestroyMutex(&gDvmJit.tableLock); + + if (gDvmJit.pJitEntryTable) { + free(gDvmJit.pJitEntryTable); + gDvmJit.pJitEntryTable = NULL; + } + + if (gDvmJit.pProfTable) { + free(gDvmJit.pProfTable); + gDvmJit.pProfTable = NULL; + } +} + +/* Returns the signed branch displacement of a Dalvik instruction. */ +int dvmGetBranchDisplacement( DecodedInstruction* decInsn ) +{ + int res = 0; + switch (dexGetInstrFormat(gDvm.instrFormat, decInsn->opCode)) { + case kFmt22t: + res = decInsn->vC; + break; + case kFmt20t: + case kFmt21t: + res = decInsn->vB; + break; + case kFmt10t: + case kFmt30t: + res = decInsn->vA; + break; + default: + dvmAbort(); + } + return res; +} + +/* + * Adds to the current trace request one instruction at a time, just + * before that instruction is interpreted. This is the primary trace + * selection function. NOTE: return instruction are handled a little + * differently. In general, instructions are "proposed" to be added + * to the current trace prior to interpretation. If the interpreter + * then successfully completes the instruction, is will be considered + * part of the request. This allows us to examine machine state prior + * to interpretation, and also abort the trace request if the instruction + * throws or does something unexpected. However, return instructions + * will cause an immediate end to the translation request - which will + * be passed to the compiler before the return completes. This is done + * in response to special handling of returns by the interpreter (and + * because returns cannot throw in a way that causes problems for the + * translated code. + */ +#define MAX_TRACE_LEN 100 +int dvmCheckJit(const u2* pc, Thread* self, InterpState* interpState) +{ + int flags,i,len; + int switchInterp = false; + int debugOrProfile = (gDvm.debuggerActive || self->suspendCount +#if defined(WITH_PROFILER) + || gDvm.activeProfilers +#endif + ); + + switch (interpState->jitState) { + char* nopStr; + int target; + int offset; + DecodedInstruction decInsn; + case kJitTSelect: + dexDecodeInstruction(gDvm.instrFormat, pc, &decInsn); +#if defined(SHOW_TRACE) + LOGD("TraceGen: adding %s",getOpcodeName(decInsn.opCode)); +#endif + flags = dexGetInstrFlags(gDvm.instrFlags, decInsn.opCode); + len = dexGetInstrOrTableWidthAbs(gDvm.instrWidth, pc); + offset = pc - interpState->method->insns; + if ((flags & kInstrNoJit) == kInstrNoJit) { + interpState->jitState = kJitTSelectEnd; + break; + } else { + if (pc != interpState->currRunHead + interpState->currRunLen) { + int currTraceRun; + /* We need to start a new trace run */ + currTraceRun = ++interpState->currTraceRun; + interpState->currRunLen = 0; + interpState->currRunHead = (u2*)pc; + interpState->trace[currTraceRun].frag.startOffset = offset; + interpState->trace[currTraceRun].frag.numInsts = 0; + interpState->trace[currTraceRun].frag.runEnd = false; + interpState->trace[currTraceRun].frag.hint = kJitHintNone; + } + interpState->trace[interpState->currTraceRun].frag.numInsts++; + interpState->totalTraceLen++; + interpState->currRunLen += len; + if ( ((flags & kInstrUnconditional) == 0) && + ((flags & (kInstrCanBranch | + kInstrCanSwitch | + kInstrCanReturn | + kInstrInvoke)) != 0)) { + interpState->jitState = kJitTSelectEnd; +#if defined(SHOW_TRACE) + LOGD("TraceGen: ending on %s, basic block end", + getOpcodeName(decInsn.opCode)); +#endif + } + if (decInsn.opCode == OP_THROW) { + interpState->jitState = kJitTSelectEnd; + } + if (interpState->totalTraceLen >= MAX_TRACE_LEN) { + interpState->jitState = kJitTSelectEnd; + } + if (debugOrProfile) { + interpState->jitState = kJitTSelectAbort; + switchInterp = !debugOrProfile; + break; + } + if ((flags & kInstrCanReturn) != kInstrCanReturn) { + break; + } + } + /* NOTE: intentional fallthrough for returns */ + case kJitTSelectEnd: + { + if (interpState->totalTraceLen == 0) { + switchInterp = !debugOrProfile; + break; + } + JitTraceDescription* desc = + (JitTraceDescription*)malloc(sizeof(JitTraceDescription) + + sizeof(JitTraceRun) * (interpState->currTraceRun+1)); + if (desc == NULL) { + LOGE("Out of memory in trace selection"); + dvmJitStopTranslationRequests(); + interpState->jitState = kJitTSelectAbort; + switchInterp = !debugOrProfile; + break; + } + interpState->trace[interpState->currTraceRun].frag.runEnd = + true; + interpState->jitState = kJitNormal; + desc->method = interpState->method; + memcpy((char*)&(desc->trace[0]), + (char*)&(interpState->trace[0]), + sizeof(JitTraceRun) * (interpState->currTraceRun+1)); +#if defined(SHOW_TRACE) + LOGD("TraceGen: trace done, adding to queue"); +#endif + dvmCompilerWorkEnqueue( + interpState->currTraceHead,kWorkOrderTrace,desc); + if (gDvmJit.blockingMode) { + dvmCompilerDrainQueue(); + } + switchInterp = !debugOrProfile; + } + break; + case kJitSingleStep: + interpState->jitState = kJitSingleStepEnd; + break; + case kJitSingleStepEnd: + interpState->entryPoint = kInterpEntryResume; + switchInterp = !debugOrProfile; + break; + case kJitTSelectAbort: +#if defined(SHOW_TRACE) + LOGD("TraceGen: trace abort"); +#endif + interpState->jitState = kJitNormal; + switchInterp = !debugOrProfile; + break; + case kJitNormal: + break; + default: + dvmAbort(); + } + return switchInterp; +} + +static inline struct JitEntry *findJitEntry(const u2* pc) +{ + int idx = dvmJitHash(pc); + + /* Expect a high hit rate on 1st shot */ + if (gDvmJit.pJitEntryTable[idx].dPC == pc) + return &gDvmJit.pJitEntryTable[idx]; + else { + int chainEndMarker = gDvmJit.maxTableEntries; + while (gDvmJit.pJitEntryTable[idx].chain != chainEndMarker) { + idx = gDvmJit.pJitEntryTable[idx].chain; + if (gDvmJit.pJitEntryTable[idx].dPC == pc) + return &gDvmJit.pJitEntryTable[idx]; + } + } + return NULL; +} + +/* + * If a translated code address exists for the davik byte code + * pointer return it. This routine needs to be fast. + */ +void* dvmJitGetCodeAddr(const u2* dPC) +{ + int idx = dvmJitHash(dPC); + + /* Expect a high hit rate on 1st shot */ + if (gDvmJit.pJitEntryTable[idx].dPC == dPC) { +#if defined(EXIT_STATS) + gDvmJit.addrLookupsFound++; +#endif + return gDvmJit.pJitEntryTable[idx].codeAddress; + } else { + int chainEndMarker = gDvmJit.maxTableEntries; + while (gDvmJit.pJitEntryTable[idx].chain != chainEndMarker) { + idx = gDvmJit.pJitEntryTable[idx].chain; + if (gDvmJit.pJitEntryTable[idx].dPC == dPC) { +#if defined(EXIT_STATS) + gDvmJit.addrLookupsFound++; +#endif + return gDvmJit.pJitEntryTable[idx].codeAddress; + } + } + } +#if defined(EXIT_STATS) + gDvmJit.addrLookupsNotFound++; +#endif + return NULL; +} + +/* + * Register the translated code pointer into the JitTable. + * NOTE: Once a codeAddress field transitions from NULL to + * JIT'd code, it must not be altered without first halting all + * threads. + */ +void dvmJitSetCodeAddr(const u2* dPC, void *nPC) { + struct JitEntry *jitEntry = findJitEntry(dPC); + assert(jitEntry); + /* Thumb code has odd PC */ + jitEntry->codeAddress = (void *) ((intptr_t) nPC |1); +} + +/* + * Determine if valid trace-bulding request is active. Return true + * if we need to abort and switch back to the fast interpreter, false + * otherwise. NOTE: may be called even when trace selection is not being + * requested + */ + +#define PROFILE_STALENESS_THRESHOLD 250000LL +bool dvmJitCheckTraceRequest(Thread* self, InterpState* interpState) +{ + bool res = false; /* Assume success */ + if (gDvmJit.pJitEntryTable != NULL) { + u8 delta = dvmGetRelativeTimeUsec() - lastProfileResetTimeUsec; + /* + * If the compiler is backlogged, or if a debugger or profiler is + * active, cancel any JIT actions + */ + if ( (gDvmJit.compilerQueueLength >= gDvmJit.compilerHighWater) || + gDvm.debuggerActive || self->suspendCount +#if defined(WITH_PROFILER) + || gDvm.activeProfilers +#endif + ) { + if (interpState->jitState != kJitOff) { + interpState->jitState = kJitNormal; + } + } else if (delta > PROFILE_STALENESS_THRESHOLD) { + resetProfileCounts(); + res = true; /* Stale profile - abort */ + } else if (interpState->jitState == kJitTSelectRequest) { + u4 chainEndMarker = gDvmJit.maxTableEntries; + u4 idx = dvmJitHash(interpState->pc); + + /* Walk the bucket chain to find an exact match for our PC */ + while ((gDvmJit.pJitEntryTable[idx].chain != chainEndMarker) && + (gDvmJit.pJitEntryTable[idx].dPC != interpState->pc)) { + idx = gDvmJit.pJitEntryTable[idx].chain; + } + + if (gDvmJit.pJitEntryTable[idx].dPC == interpState->pc) { + /* + * Got a match. This means a trace has already + * been requested for this address. Bail back to + * mterp, which will check if the translation is ready + * for execution + */ + interpState->jitState = kJitTSelectAbort; + } else { + /* + * No match. Aquire jitTableLock and find the last + * slot in the chain. Possibly continue the chain walk in case + * some other thread allocated the slot we were looking + * at previuosly + */ + dvmLockMutex(&gDvmJit.tableLock); + /* + * At this point, if .dPC is NULL, then the slot we're + * looking at is the target slot from the primary hash + * (the simple, and expected case). Otherwise we're going + * to have to find a free slot and chain it. + */ + MEM_BARRIER(); + if (gDvmJit.pJitEntryTable[idx].dPC != NULL) { + u4 prev; + while (gDvmJit.pJitEntryTable[idx].chain != chainEndMarker) { + idx = gDvmJit.pJitEntryTable[idx].chain; + } + /* Here, idx should be pointing to the last cell of an + * active chain whose last member contains a valid dPC */ + assert(gDvmJit.pJitEntryTable[idx].dPC != NULL); + /* Now, do a linear walk to find a free cell and add it to + * end of this chain */ + prev = idx; + while (true) { + idx++; + if (idx == chainEndMarker) + idx = 0; /* Wraparound */ + if ((gDvmJit.pJitEntryTable[idx].dPC == NULL) || + (idx == prev)) + break; + } + if (idx != prev) { + /* Got it - chain */ + gDvmJit.pJitEntryTable[prev].chain = idx; + } + } + if (gDvmJit.pJitEntryTable[idx].dPC == NULL) { + /* Allocate the slot */ + gDvmJit.pJitEntryTable[idx].dPC = interpState->pc; + } else { + /* + * Table is full. We could resize it, but that would + * be better handled by the translator thread. It + * will be aware of how full the table is getting. + * Disable further profiling and continue. + */ + interpState->jitState = kJitTSelectAbort; + LOGD("JIT: JitTable full, disabling profiling"); + dvmJitStopTranslationRequests(); + } + dvmUnlockMutex(&gDvmJit.tableLock); + } + } + switch (interpState->jitState) { + case kJitTSelectRequest: + interpState->jitState = kJitTSelect; + interpState->currTraceHead = interpState->pc; + interpState->currTraceRun = 0; + interpState->totalTraceLen = 0; + interpState->currRunHead = interpState->pc; + interpState->currRunLen = 0; + interpState->trace[0].frag.startOffset = + interpState->pc - interpState->method->insns; + interpState->trace[0].frag.numInsts = 0; + interpState->trace[0].frag.runEnd = false; + interpState->trace[0].frag.hint = kJitHintNone; + break; + case kJitTSelect: + case kJitTSelectAbort: + res = true; + case kJitSingleStep: + case kJitSingleStepEnd: + case kJitOff: + case kJitNormal: + break; + default: + dvmAbort(); + } + } + return res; +} + +#endif /* WITH_JIT */ |