Dalvik JIT工作流程

来源:互联网 发布:上海网络教育大学报名 编辑:程序博客网 时间:2024/05/21 09:24

Dalvik JIT是以trace为单位进行编译的,什么是trace请参考这里

首先,我们需要找到JIT的入口点,在Dalvik VM里面这是在汇编解释器中的common_updateProfile

基本的工作流程:

  • 首先需要注意的是,并不是一上来JIT就会工作,而是需要在汇编解释器中跑过2n+1遍之后的代码才会被JIT,这里的n是threshold,在ICS上取值为40,那个1是表示JIT编译好后至少要在debugger模式下跑上一遍确保不出问题。Dalvik同时用了一个小小的trick去检查某一个地址开始的代码是否已经被JIT编译了,那就是拿当前的Dalvik PC(rPC)来做一个简单的hash运算得到一个值,然后查表。关于这个是否会碰撞,也有人提问和回答,参见google groups
  • 若完成了2n遍的解释,Dalvik会调用函数dvmJitGetTraceAddrThread() 来获取编译好的代码; 否则的话继续运行Dalvik code(GOTO_OPCODE_IFNE(ip));
  • 若函数dvmJitGetTraceAddrThread()返回non-zero值,表示可以直接执行编译好的trace,通过"bxne r0"跳转过去执行;
  • 否则,调用dvmJitCheckTraceRequest()来启动编译线程去编译trace,并继续执行Dalvik code;
  • 若编译选项WITH_SELF_VERIFICATION开启,函数dvmCompilerGetInterpretTemplate()会被调用(作用是啥?)

并非所有的dex opcode都会去执行common_updateProfile,虚拟机仅在如下dex code情况下开始JIT执行:

  • dvmMterpStdRun
  • L_OP_PACKED_SWITCH
  • L_OP_SPARSE_SWITCH
  • L_OP_IF_EQ
  • L_OP_IF_NE
  • L_OP_IF_LT
  • L_OP_IF_GE
  • L_OP_IF_GT
  • L_OP_IF_LE
  • L_OP_IF_EQZ
  • L_OP_IF_NEZ
  • L_OP_IF_LTZ
  • L_OP_IF_GEZ
  • L_OP_IF_GTZ
  • L_OP_IF_LEZ
    也就是说,在这些分支语句上Dalvik才会进行JIT Check

若代码被执行次数到达threshold值(40 in ICS),Dalvik接下来会执行dvmJitCheckTraceRequest(),调用过程是在汇编中进行的,其逻辑伪代码为

  1. if Thread->subMode == (kSubModeJitTraceBuild | kSubModeJitSV), then
  2. Thread->jitState = kJitTSelectRequest;
  3. dvmJitCheckTraceRequest();
  4. endif

dvmJitCheckTraceRequest() 将会初始化Thread结构体中的的struct trace

  1. dvmJitCheckTraceRequest()
  2. {
  3. switch (self->jitState)
  4. {
  5. case kJitTSelectRequest:
  6. case kJitTSelectRequestHot:
  7. self->jitState = kJitTSelect;
  8. self->traceMethod = self->interpSave.method;
  9. self->currTraceHead = self->interpSave.pc;
  10. self->currTraceRun = 0;
  11. self->totalTraceLen = 0;
  12. self->currRunHead = self->interpSave.pc;
  13. self->currRunLen = 0;
  14. self->trace[0].info.frag.startOffset = self->interpSave.pc - self->interpSave.method->insns;
  15. self->trace[0].info.frag.numInsts = 0;
  16. self->trace[0].info.frag.runEnd = false;
  17. self->trace[0].info.frag.hint = kJitHintNone;
  18. self->trace[0].isCode = true;
  19. self->lastPC = 0;
  20. /* Turn on trace selection mode */
  21. dvmEnableSubMode(self, kSubModeJitTraceBuild);
  22. }

dvmJitCheckTraceRequest()在上面代码的最后一行调用了dvmEnableSubMode(kSubModeJitTraceBuild),而这个dvmEnableSubMode()函数接下来会调用updateInterpBreak()

  1. void updateInterpBreak(Thread* thread, ExecutionSubModes subMode, bool enable)
  2. {
  3. InterpBreak oldValue, newValue;
  4. do {
  5. oldValue = newValue = thread->interpBreak;
  6. newValue.ctl.breakFlags = kInterpNoBreak; // Assume full reset
  7. if (enable)
  8. newValue.ctl.subMode |= subMode;
  9. else
  10. newValue.ctl.subMode &= ~subMode;
  11. if (newValue.ctl.subMode & SINGLESTEP_BREAK_MASK)
  12. newValue.ctl.breakFlags |= kInterpSingleStep;
  13. if (newValue.ctl.subMode & SAFEPOINT_BREAK_MASK)
  14. newValue.ctl.breakFlags |= kInterpSafePoint;
  15. newValue.ctl.curHandlerTable = (newValue.ctl.breakFlags) ?
  16. thread->altHandlerTable : thread->mainHandlerTable;
  17. } while (dvmQuasiAtomicCas64(oldValue.all, newValue.all,
  18. &thread->interpBreak.all) != 0);
  19. }

于是在dvmJitCheckTraceRequest()的最后执行阶段,我们可以看到如下状态:

  • thread->jitState = kJitTSelect;
  • thread->interpBreak.all = kInterpSiggleStep;
  • thread->subMode = kSubModeJitTraceBuild;

In addition, at red line, the curHandleTable is set to thread->altHandlerTable. That means dvmCheckBefore() will be called exactly before executing every dalvik code. dvmCheckBefore(), in turn, call dvmCheckJit() if subMode = kSubModeJitTraceBuild. It is very important for building jit trace.

This is one of dalvik code interpreter snippet in altHandlerTable

  1. 9742 ldrb r3, [rSELF, #offThread_breakFlags]           <==get breakFlag
  2. 19743 adrl lr, dvmAsmInstructionStart + (232 * 64)      <==lr points to real handler
  3. 19744 ldr rIBASE, [rSELF, #offThread_curHandlerTable]  
  4. 19745 cmp r3, #0                                                           
  5. 19746 bxeq lr @ nothing to do - jump to real handler    <==if breakFlag is 0, jmp to real handler
  6. 19747 EXPORT_PC()                                                    <==otherwise, call dvmCheckBefore()
  7. 19748 mov r0, rPC @ arg0
  8. 19749 mov r1, rFP @ arg1
  9. 19750 mov r2, rSELF @ arg2
  10. 19751 b dvmCheckBefore @ (dPC,dFP,self) tail call     <==since lr points to real handle, dvmCheckBefore() return to real handler

In dvmCheckJit(),

  1. ...
  2.             self->trace[self->currTraceRun].info.frag.numInsts++;
  3.             self->totalTraceLen++;
  4.             self->currRunLen+= len;
  5. ...
if met a jmp/invoke/return/switch/throw instruction, the building jit trace will be done:
  1. self->jitState= kJitTSelectEnd;
Once the trace build done, dvmCheckJit() calls dvmCompilerWorkEnqueue() to enqueue the trace to JIT compiler queue. The meanings of the trace fields at this time:
  • self->trace[0].info.frag.startOffset: offset from jit start pc to first method instruction;
  • self->trace[0].info.frag.numInsts: total number of instructions;
  • self->trace[0].info.frag.runEnd = true;
  • self->trace[0].info.frag.hint = kJitHintNone;
  • self->trace[0].isCode = true;
In essential, the trace record the start dalvik pc offset and number of instruction after start pc.

This is simplest trace description. More complicated traces includes some meta info and multiply trace descriptions. please see insertClassMethodInfo() if met a invoke code during building jit trace.

After that, dvmCheckJit() calls dvmDisableSubMode(self, kSubModeJitTraceBuild) to disable kSubModeJitTraceBuild. at this time, the curHandleTable is restored to thread->mainHandlerTable and stop mode of the single step.

2. Compile JIT trace

Compiling dalvik code in jit trace is in separate thread. The dvmCompilerDoWork(&work) is main function. The concrete compile procedures is complicated, I can't understand it currently. 

The result of compile is saved in JitEntry::codeAddress. dalvik has JitEntry array, which is initialized to 4096 in arm arch.

When hot trace was compiled to machine code, dalvik calculates the JitEntry array index based on hash function dvmJitHash():
  1. 90 static inline u4 dvmJitHashMask(const u2* p, u4 mask) {
  2.      91 return ((((u4)p>>12)^(u4)p)>>1)& (mask);
  3.      92 }
  4.      93
  5.      94 static inline u4 dvmJitHash( const u2* p ) {
  6.      95 return dvmJitHashMask( p, gDvmJit.jitTableMask);
  7.      96 }
The input ofdvmJitHash() is dalvik PC. If the index is occupied, the dalvik find out next 
free entry and set currentu.info.chain = next free index;

The translate code is stored in share memory named by"dalvik-jit-code-cache", the initialized size is 1M.
The codeAddress in jitEntry points to that code cache.

3 Summary
When met jump dex-code, dalvik calculates the executing times for this dex-code. If the 
timesis more than 40(in ICS), dalvik lunch JIT trace build process. In that process, the
dalvik is actually in single step mode, call dvmCheckBefore() before real dex-code. In
dvmCheckBeofore(), the number of decx-code is recorded. When dalvik meets jump code
again, the JIT trace build process is ended. The dalvik feed the trace to compiler thread.
The compiler thread output the compile result in array of JitEntry. The index of array is
hash value for dalvik PC value.

原创粉丝点击