| /* |
| * Copyright (C) 2009 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <sys/mman.h> |
| #include <errno.h> |
| |
| #include "Dalvik.h" |
| #include "interp/Jit.h" |
| #include "CompilerInternals.h" |
| |
| static inline bool workQueueLength(void) |
| { |
| return gDvmJit.compilerQueueLength; |
| } |
| |
| static CompilerWorkOrder workDequeue(void) |
| { |
| assert(gDvmJit.compilerWorkQueue[gDvmJit.compilerWorkDequeueIndex].kind |
| != kWorkOrderInvalid); |
| CompilerWorkOrder work = |
| gDvmJit.compilerWorkQueue[gDvmJit.compilerWorkDequeueIndex]; |
| gDvmJit.compilerWorkQueue[gDvmJit.compilerWorkDequeueIndex++].kind = |
| kWorkOrderInvalid; |
| if (gDvmJit.compilerWorkDequeueIndex == COMPILER_WORK_QUEUE_SIZE) { |
| gDvmJit.compilerWorkDequeueIndex = 0; |
| } |
| gDvmJit.compilerQueueLength--; |
| if (gDvmJit.compilerQueueLength == 0) { |
| int cc = pthread_cond_signal(&gDvmJit.compilerQueueEmpty); |
| } |
| |
| /* Remember the high water mark of the queue length */ |
| if (gDvmJit.compilerQueueLength > gDvmJit.compilerMaxQueued) |
| gDvmJit.compilerMaxQueued = gDvmJit.compilerQueueLength; |
| |
| return work; |
| } |
| |
| bool dvmCompilerWorkEnqueue(const u2 *pc, WorkOrderKind kind, void* info) |
| { |
| int cc; |
| int i; |
| int numWork; |
| int oldStatus = dvmChangeStatus(NULL, THREAD_VMWAIT); |
| bool result = true; |
| |
| dvmLockMutex(&gDvmJit.compilerLock); |
| |
| /* |
| * Return if queue is full. |
| * If the code cache is full, we will allow the work order to be added and |
| * we use that to trigger code cache reset. |
| */ |
| if (gDvmJit.compilerQueueLength == COMPILER_WORK_QUEUE_SIZE) { |
| result = false; |
| goto done; |
| } |
| |
| for (numWork = gDvmJit.compilerQueueLength, |
| i = gDvmJit.compilerWorkDequeueIndex; |
| numWork > 0; |
| numWork--) { |
| /* Already enqueued */ |
| if (gDvmJit.compilerWorkQueue[i++].pc == pc) |
| goto done; |
| /* Wrap around */ |
| if (i == COMPILER_WORK_QUEUE_SIZE) |
| i = 0; |
| } |
| |
| CompilerWorkOrder *newOrder = |
| &gDvmJit.compilerWorkQueue[gDvmJit.compilerWorkEnqueueIndex]; |
| newOrder->pc = pc; |
| newOrder->kind = kind; |
| newOrder->info = info; |
| newOrder->result.codeAddress = NULL; |
| newOrder->result.discardResult = |
| (kind == kWorkOrderTraceDebug || kind == kWorkOrderICPatch) ? |
| true : false; |
| newOrder->result.requestingThread = dvmThreadSelf(); |
| |
| gDvmJit.compilerWorkEnqueueIndex++; |
| if (gDvmJit.compilerWorkEnqueueIndex == COMPILER_WORK_QUEUE_SIZE) |
| gDvmJit.compilerWorkEnqueueIndex = 0; |
| gDvmJit.compilerQueueLength++; |
| cc = pthread_cond_signal(&gDvmJit.compilerQueueActivity); |
| assert(cc == 0); |
| |
| done: |
| dvmUnlockMutex(&gDvmJit.compilerLock); |
| dvmChangeStatus(NULL, oldStatus); |
| return result; |
| } |
| |
| /* Block until queue length is 0 */ |
| void dvmCompilerDrainQueue(void) |
| { |
| int oldStatus = dvmChangeStatus(NULL, THREAD_VMWAIT); |
| dvmLockMutex(&gDvmJit.compilerLock); |
| while (workQueueLength() != 0 && !gDvmJit.haltCompilerThread) { |
| pthread_cond_wait(&gDvmJit.compilerQueueEmpty, &gDvmJit.compilerLock); |
| } |
| dvmUnlockMutex(&gDvmJit.compilerLock); |
| dvmChangeStatus(NULL, oldStatus); |
| } |
| |
| bool dvmCompilerSetupCodeCache(void) |
| { |
| extern void dvmCompilerTemplateStart(void); |
| extern void dmvCompilerTemplateEnd(void); |
| |
| /* Allocate the code cache */ |
| gDvmJit.codeCache = mmap(0, CODE_CACHE_SIZE, |
| PROT_READ | PROT_WRITE | PROT_EXEC, |
| MAP_PRIVATE | MAP_ANON, -1, 0); |
| if (gDvmJit.codeCache == MAP_FAILED) { |
| LOGE("Failed to create the code cache: %s\n", strerror(errno)); |
| return false; |
| } |
| |
| // For debugging only |
| // LOGD("Code cache starts at %p", gDvmJit.codeCache); |
| |
| /* Copy the template code into the beginning of the code cache */ |
| int templateSize = (intptr_t) dmvCompilerTemplateEnd - |
| (intptr_t) dvmCompilerTemplateStart; |
| memcpy((void *) gDvmJit.codeCache, |
| (void *) dvmCompilerTemplateStart, |
| templateSize); |
| |
| gDvmJit.templateSize = templateSize; |
| gDvmJit.codeCacheByteUsed = templateSize; |
| |
| /* Only flush the part in the code cache that is being used now */ |
| cacheflush((intptr_t) gDvmJit.codeCache, |
| (intptr_t) gDvmJit.codeCache + templateSize, 0); |
| return true; |
| } |
| |
| static void crawlDalvikStack(Thread *thread, bool print) |
| { |
| void *fp = thread->curFrame; |
| StackSaveArea* saveArea = NULL; |
| int stackLevel = 0; |
| |
| if (print) { |
| LOGD("Crawling tid %d (%s / %p %s)", thread->systemTid, |
| dvmGetThreadStatusStr(thread->status), |
| thread->inJitCodeCache, |
| thread->inJitCodeCache ? "jit" : "interp"); |
| } |
| /* Crawl the Dalvik stack frames to clear the returnAddr field */ |
| while (fp != NULL) { |
| saveArea = SAVEAREA_FROM_FP(fp); |
| |
| if (print) { |
| if (dvmIsBreakFrame(fp)) { |
| LOGD(" #%d: break frame (%p)", |
| stackLevel, saveArea->returnAddr); |
| } |
| else { |
| LOGD(" #%d: %s.%s%s (%p)", |
| stackLevel, |
| saveArea->method->clazz->descriptor, |
| saveArea->method->name, |
| dvmIsNativeMethod(saveArea->method) ? |
| " (native)" : "", |
| saveArea->returnAddr); |
| } |
| } |
| stackLevel++; |
| saveArea->returnAddr = NULL; |
| assert(fp != saveArea->prevFrame); |
| fp = saveArea->prevFrame; |
| } |
| /* Make sure the stack is fully unwound to the bottom */ |
| assert(saveArea == NULL || |
| (u1 *) (saveArea+1) == thread->interpStackStart); |
| } |
| |
| static void resetCodeCache(void) |
| { |
| Thread* thread; |
| u8 startTime = dvmGetRelativeTimeUsec(); |
| int inJit = 0; |
| |
| LOGD("Reset the JIT code cache (%d bytes used / %d time(s))", |
| gDvmJit.codeCacheByteUsed, ++gDvmJit.numCodeCacheReset); |
| |
| /* Stop the world */ |
| dvmSuspendAllThreads(SUSPEND_FOR_CC_RESET); |
| |
| /* If any thread is found stuck in the JIT state, don't reset the cache */ |
| for (thread = gDvm.threadList; thread != NULL; thread = thread->next) { |
| if (thread->inJitCodeCache) { |
| inJit++; |
| /* |
| * STOPSHIP |
| * Change the verbose mode to false after the new code receives |
| * more QA love. |
| */ |
| crawlDalvikStack(thread, true); |
| } |
| } |
| |
| if (inJit) { |
| /* Wait a while for the busy threads to rest and try again */ |
| gDvmJit.delayCodeCacheReset = 256; |
| goto done; |
| } |
| |
| /* Drain the work queue to free the work order */ |
| while (workQueueLength()) { |
| CompilerWorkOrder work = workDequeue(); |
| free(work.info); |
| } |
| |
| /* Wipe out the returnAddr field that soon will point to stale code */ |
| for (thread = gDvm.threadList; thread != NULL; thread = thread->next) { |
| crawlDalvikStack(thread, false); |
| } |
| |
| /* Reset the JitEntry table contents to the initial unpopulated state */ |
| dvmJitResetTable(); |
| |
| /* |
| * Wipe out the code cache content to force immediate crashes if |
| * stale JIT'ed code is invoked. |
| */ |
| memset((char *) gDvmJit.codeCache + gDvmJit.templateSize, |
| 0, |
| gDvmJit.codeCacheByteUsed - gDvmJit.templateSize); |
| cacheflush((intptr_t) gDvmJit.codeCache, |
| (intptr_t) gDvmJit.codeCache + gDvmJit.codeCacheByteUsed, 0); |
| |
| /* Reset the current mark of used bytes to the end of template code */ |
| gDvmJit.codeCacheByteUsed = gDvmJit.templateSize; |
| gDvmJit.numCompilations = 0; |
| |
| /* Reset the work queue */ |
| memset(gDvmJit.compilerWorkQueue, 0, |
| sizeof(CompilerWorkOrder) * COMPILER_WORK_QUEUE_SIZE); |
| gDvmJit.compilerWorkEnqueueIndex = gDvmJit.compilerWorkDequeueIndex = 0; |
| gDvmJit.compilerQueueLength = 0; |
| |
| /* All clear now */ |
| gDvmJit.codeCacheFull = false; |
| |
| LOGD("Code cache reset takes %lld usec", |
| dvmGetRelativeTimeUsec() - startTime); |
| |
| done: |
| /* Resume all threads */ |
| dvmResumeAllThreads(SUSPEND_FOR_CC_RESET); |
| } |
| |
| static void *compilerThreadStart(void *arg) |
| { |
| dvmChangeStatus(NULL, THREAD_VMWAIT); |
| |
| /* |
| * Wait a little before recieving translation requests on the assumption |
| * that process start-up code isn't worth compiling. The trace |
| * selector won't attempt to request a translation if the queue is |
| * filled, so we'll prevent by keeping the high water mark at zero |
| * for a shore time. |
| */ |
| assert(gDvmJit.compilerHighWater == 0); |
| usleep(1000); |
| gDvmJit.compilerHighWater = |
| COMPILER_WORK_QUEUE_SIZE - (COMPILER_WORK_QUEUE_SIZE/4); |
| |
| dvmLockMutex(&gDvmJit.compilerLock); |
| /* |
| * Since the compiler thread will not touch any objects on the heap once |
| * being created, we just fake its state as VMWAIT so that it can be a |
| * bit late when there is suspend request pending. |
| */ |
| while (!gDvmJit.haltCompilerThread) { |
| if (workQueueLength() == 0) { |
| int cc; |
| cc = pthread_cond_signal(&gDvmJit.compilerQueueEmpty); |
| assert(cc == 0); |
| pthread_cond_wait(&gDvmJit.compilerQueueActivity, |
| &gDvmJit.compilerLock); |
| continue; |
| } else { |
| do { |
| CompilerWorkOrder work = workDequeue(); |
| dvmUnlockMutex(&gDvmJit.compilerLock); |
| /* Check whether there is a suspend request on me */ |
| dvmCheckSuspendPending(NULL); |
| /* Is JitTable filling up? */ |
| if (gDvmJit.jitTableEntriesUsed > |
| (gDvmJit.jitTableSize - gDvmJit.jitTableSize/4)) { |
| dvmJitResizeJitTable(gDvmJit.jitTableSize * 2); |
| } |
| if (gDvmJit.haltCompilerThread) { |
| LOGD("Compiler shutdown in progress - discarding request"); |
| } else { |
| /* If compilation failed, use interpret-template */ |
| if (!dvmCompilerDoWork(&work)) { |
| work.result.codeAddress = gDvmJit.interpretTemplate; |
| } |
| if (!work.result.discardResult) { |
| dvmJitSetCodeAddr(work.pc, work.result.codeAddress, |
| work.result.instructionSet); |
| } |
| } |
| free(work.info); |
| dvmLockMutex(&gDvmJit.compilerLock); |
| |
| /* |
| * FIXME - temporarily disable code cache reset until |
| * stale code stops leaking. |
| */ |
| #if 0 |
| if (gDvmJit.codeCacheFull == true) { |
| if (gDvmJit.delayCodeCacheReset == 0) { |
| resetCodeCache(); |
| assert(workQueueLength() == 0 || |
| gDvmJit.delayCodeCacheReset != 0); |
| } else { |
| LOGD("Delay the next %d tries to reset code cache", |
| gDvmJit.delayCodeCacheReset); |
| gDvmJit.delayCodeCacheReset--; |
| } |
| } |
| #endif |
| } while (workQueueLength() != 0); |
| } |
| } |
| pthread_cond_signal(&gDvmJit.compilerQueueEmpty); |
| dvmUnlockMutex(&gDvmJit.compilerLock); |
| |
| /* |
| * As part of detaching the thread we need to call into Java code to update |
| * the ThreadGroup, and we should not be in VMWAIT state while executing |
| * interpreted code. |
| */ |
| dvmChangeStatus(NULL, THREAD_RUNNING); |
| |
| LOGD("Compiler thread shutting down\n"); |
| return NULL; |
| } |
| |
| bool dvmCompilerStartup(void) |
| { |
| /* Make sure the BBType enum is in sane state */ |
| assert(kChainingCellNormal == 0); |
| |
| /* Architecture-specific chores to initialize */ |
| if (!dvmCompilerArchInit()) |
| goto fail; |
| |
| /* |
| * Setup the code cache if it is not done so already. For apps it should be |
| * done by the Zygote already, but for command-line dalvikvm invocation we |
| * need to do it here. |
| */ |
| if (gDvmJit.codeCache == NULL) { |
| if (!dvmCompilerSetupCodeCache()) |
| goto fail; |
| } |
| |
| /* Allocate the initial arena block */ |
| if (dvmCompilerHeapInit() == false) { |
| goto fail; |
| } |
| |
| dvmInitMutex(&gDvmJit.compilerLock); |
| pthread_cond_init(&gDvmJit.compilerQueueActivity, NULL); |
| pthread_cond_init(&gDvmJit.compilerQueueEmpty, NULL); |
| |
| dvmLockMutex(&gDvmJit.compilerLock); |
| |
| gDvmJit.haltCompilerThread = false; |
| |
| /* Reset the work queue */ |
| memset(gDvmJit.compilerWorkQueue, 0, |
| sizeof(CompilerWorkOrder) * COMPILER_WORK_QUEUE_SIZE); |
| gDvmJit.compilerWorkEnqueueIndex = gDvmJit.compilerWorkDequeueIndex = 0; |
| gDvmJit.compilerQueueLength = 0; |
| /* Block new entries via HighWater until compiler thread is ready */ |
| gDvmJit.compilerHighWater = 0; |
| |
| assert(gDvmJit.compilerHighWater < COMPILER_WORK_QUEUE_SIZE); |
| if (!dvmCreateInternalThread(&gDvmJit.compilerHandle, "Compiler", |
| compilerThreadStart, NULL)) { |
| dvmUnlockMutex(&gDvmJit.compilerLock); |
| goto fail; |
| } |
| |
| /* Track method-level compilation statistics */ |
| gDvmJit.methodStatsTable = dvmHashTableCreate(32, NULL); |
| |
| dvmUnlockMutex(&gDvmJit.compilerLock); |
| |
| return true; |
| |
| fail: |
| return false; |
| } |
| |
| void dvmCompilerShutdown(void) |
| { |
| void *threadReturn; |
| |
| if (gDvmJit.compilerHandle) { |
| |
| gDvmJit.haltCompilerThread = true; |
| |
| dvmLockMutex(&gDvmJit.compilerLock); |
| pthread_cond_signal(&gDvmJit.compilerQueueActivity); |
| dvmUnlockMutex(&gDvmJit.compilerLock); |
| |
| if (pthread_join(gDvmJit.compilerHandle, &threadReturn) != 0) |
| LOGW("Compiler thread join failed\n"); |
| else |
| LOGD("Compiler thread has shut down\n"); |
| } |
| } |