Skip to content

Dalvik虚拟机执行过程——JIT #10

@NazcaLines

Description

@NazcaLines

#8 #9

JIT简介

JIT分类
  1. Method-based JIT 优化整个方法,优化比较彻底,但需要前期预热。
  2. Trace-based JIT 优化一小段代码,优化粒度细,消耗的内存页比较小。

Dalvik是采用Trace-based JIT.下面的文章文成2部分,第一部分是dalvik虚拟机主线程在执行指令时都发生了什么事情 第二部分是:JIT线程如何启动,并且如何处理JIT请求。

Dalvik取指令执行

记得在 #9 中,我们忽略了JIT部分,在这里补上。

#if defined(WITH_JIT)
.LentryInstr:
    /* Entry is always a possible trace start */
    ldr     r0, [rSELF, #offThread_pJitProfTable]
    FETCH_INST()
    mov     r1, #0                      @ prepare the value for the new state
    str     r1, [rSELF, #offThread_inJitCodeCache] @ back to the interp land
    cmp     r0,#0                       @ is profiling disabled?
#if !defined(WITH_SELF_VERIFICATION)
    bne     common_updateProfile        @ profiling is enabled
#else
    ldr     r2, [rSELF, #offThread_shadowSpace] @ to find out the jit exit state
    beq     1f                          @ profiling is disabled
    ldr     r3, [r2, #offShadowSpace_jitExitState]  @ jit exit state
    cmp     r3, #kSVSTraceSelect        @ hot trace following?
    moveq   r2,#kJitTSelectRequestHot   @ ask for trace selection
    beq     common_selectTrace          @ go build the trace
    cmp     r3, #kSVSNoProfile          @ don't profile the next instruction?
    beq     1f                          @ intrepret the next instruction
    b       common_updateProfile        @ collect profiles
#endif
1:
    GET_INST_OPCODE(ip)
    GOTO_OPCODE(ip)

在上面这段代码中,我们看到 有创建trace的步骤,也有

b       common_updateProfile 

common_updateProfile中会去检测有没有到达阈值,如果检测到已经过了阈值了,那么会交给JIT去优化,否则回到解释器继续执行。

common_updateProfile:
    eor     r3,rPC,rPC,lsr #12 @ cheap, but fast hash function
    lsl     r3,r3,#(32 - JIT_PROF_SIZE_LOG_2)          @ shift out excess bits
    ldrb    r1,[r0,r3,lsr #(32 - JIT_PROF_SIZE_LOG_2)] @ get counter
    GET_INST_OPCODE(ip)
    subs    r1,r1,#1           @ decrement counter
    strb    r1,[r0,r3,lsr #(32 - JIT_PROF_SIZE_LOG_2)] @ and store it
    GOTO_OPCODE_IFNE(ip)       @ if not threshold, fallthrough otherwise */

要是过了阈值了,那么不会去走GOTO_OPCODE_IFNE(ip) ,而是继续下面的代码:

/* Looks good, reset the counter */
    ldr     r1, [rSELF, #offThread_jitThreshold]
    strb    r1,[r0,r3,lsr #(32 - JIT_PROF_SIZE_LOG_2)] @ reset counter
//这里,某一代码片段的计数已经过了阈值了,那么重置该计数。
    EXPORT_PC()
    mov     r0,rPC
    mov     r1,rSELF
    bl      dvmJitGetTraceAddrThread    @ (pc, self)
//获取二进制代码所在内存。
    str     r0, [rSELF, #offThread_inJitCodeCache] @ set the inJitCodeCache flag
    mov     r1, rPC                     @ arg1 of translation may need this
    mov     lr, #0                      @  in case target is HANDLER_INTERPRET
    cmp     r0,#0
//判断内存地址是否是0
#if !defined(WITH_SELF_VERIFICATION)
    bxne    r0                          @ jump to the translation
    mov     r2,#kJitTSelectRequest      @ ask for trace selection
    @ fall-through to common_selectTrace
#else
//如果不是0,则发送构造路径请求,并跳转执行
    moveq   r2,#kJitTSelectRequest      @ ask for trace selection
    beq     common_selectTrace
    /*
     * At this point, we have a target translation.  However, if
     * that translation is actually the interpret-only pseudo-translation
     * we want to treat it the same as no translation.
     */
    mov     r10, r0                     @ save target
    bl      dvmCompilerGetInterpretTemplate
    cmp     r0, r10                     @ special case?
    bne     jitSVShadowRunStart         @ set up self verification shadow space
    @ Need to clear the inJitCodeCache flag
    mov    r3, #0                       @ 0 means not in the JIT code cache
    str    r3, [rSELF, #offThread_inJitCodeCache] @ back to the interp land
    GET_INST_OPCODE(ip)
    GOTO_OPCODE(ip)
    /* no return */
#endif

common_selectTrace
中,会调用c代码去初始化环境,之后每次运行都会在去这里检查了。

common_selectTrace:
...
bl      dvmJitCheckTraceRequest
//关于JIt的相关信息,都在全全局变量struct DvmJitGlobals gDvmJit;中。

在dalvik真正开始执行的时候,JIT模式下和FAST模式下,跳转的指令处理代码不同。在JIT模式下,比如一个MOV指令,会跳转到这里

dvmAsmAltInstructionStart = .L_ALT_OP_NOP
/* ------------------------------ */
    .balign 64
.L_ALT_OP_NOP: /* 0x00 */
/* File: armv5te/alt_stub.S */
/*
 * Inter-instruction transfer stub.  Call out to dvmCheckBefore to handle
 * any interesting requests and then jump to the real instruction
 * handler.    Note that the call to dvmCheckBefore is done as a tail call.
 * rIBASE updates won't be seen until a refresh, and we can tell we have a
 * stale rIBASE if breakFlags==0.  Always refresh rIBASE here, and then
 * bail to the real handler if breakFlags==0.
 */
    ldrb   r3, [rSELF, #offThread_breakFlags]
    adrl   lr, dvmAsmInstructionStart + (0 * 64)
    ldr    rIBASE, [rSELF, #offThread_curHandlerTable]
    cmp    r3, #0
    bxeq   lr                   @ nothing to do - jump to real handler
    EXPORT_PC()
    mov    r0, rPC              @ arg0
    mov    r1, rFP              @ arg1
    mov    r2, rSELF            @ arg2
    b      dvmCheckBefore       @ (dPC,dFP,self) tail call

从代码中可以看到,会调用dvmCheckBefore,这个函数就是会处理JIT路径的相关问题,所以每一个指令都会进入到JIT去扩充Trace,当满足一定条件时,Trace建立结束:

  1. 如果Trace中指令数不为0,且当前指令时SWITCH,则建立结束。
  2. throw抛出异常,则结束建立。
  3. Trace中指令数目最大为100,超出则建立结束。
  4. 遇到return,建立结束
  5. 如果不是GOTO指令,且是Branch,Switch,Return,Invoke指令,则建立结束。

这些Trace会加入Trace队列,等待JIT线程去编译处理。

JIT线程的建立与执行

dvmStartup() -> dvmInitAfterZygote()->dvmCompilerStartup()->dvmCreateInternalThread
在dvmCreateInternalThread中,会调用linux的pthread_create方法,创建一个线程,并传入需要线程执行的函数compilerThreadStart,所以,现在JIT线程启动了,并去执行compilerThreadStart函数。
compilerThreadStart中:

  1. 初始化了存放函数指针的table并进行其他初始化操作
  2. 只要!gDvmJit.haltCompilerThread则不停的从workDequeue中取Trace,如果队列为空,则等待,否则取出Trace,调用dvmCompilerDoWork(&work);进行处理。dvmCompilerDoWork->dvmCompileTrace编译部分暂不学习,待以后回头看

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions