/*
 * 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.
 */
/*
 * Common subroutines and data.
 */

#if defined(WITH_JIT)
/*
 * JIT-related re-entries into the interpreter.  In general, if the
 * exit from a translation can at some point be chained, the entry
 * here requires that control arrived via a call, and that the "rp"
 * on TOS is actually a pointer to a 32-bit cell containing the Dalvik PC
 * of the next insn to handle.  If no chaining will happen, the entry
 * should be reached via a direct jump and rPC set beforehand.
 */

    .global dvmJitToInterpPunt
/*
 * The compiler will generate a jump to this entry point when it is
 * having difficulty translating a Dalvik instruction.  We must skip
 * the code cache lookup & prevent chaining to avoid bouncing between
 * the interpreter and code cache. rPC must be set on entry.
 */
dvmJitToInterpPunt:
    GET_PC
#if defined(WITH_JIT_TUNING)
    movl   rPC, OUT_ARG0(%esp)
    call   dvmBumpPunt
#endif
    movl   rSELF, %ecx
    movl   offThread_curHandlerTable(%ecx),rIBASE
    movl        $$0, offThread_inJitCodeCache(%ecx)
    FETCH_INST_R %ecx
    GOTO_NEXT_R %ecx

    .global dvmJitToInterpSingleStep
/*
 * Return to the interpreter to handle a single instruction.
 * Should be reached via a call.
 * On entry:
 *   0(%esp)          <= native return address within trace
 *   rPC              <= Dalvik PC of this instruction
 *   OUT_ARG0+4(%esp) <= Dalvik PC of next instruction
 */
dvmJitToInterpSingleStep:
/* TODO */
    call     dvmAbort
#if 0
    pop    %eax
    movl   rSELF, %ecx
    movl   OUT_ARG0(%esp), %edx
    movl   %eax,offThread_jitResumeNPC(%ecx)
    movl   %edx,offThread_jitResumeDPC(%ecx)
    movl   $$kInterpEntryInstr,offThread_entryPoint(%ecx)
    movl   $$1,rINST     # changeInterp <= true
    jmp    common_gotoBail
#endif

    .global dvmJitToInterpNoChainNoProfile
/*
 * Return from the translation cache to the interpreter to do method
 * invocation.  Check if the translation exists for the callee, but don't
 * chain to it. rPC must be set on entry.
 */
dvmJitToInterpNoChainNoProfile:
#if defined(WITH_JIT_TUNING)
    call   dvmBumpNoChain
#endif
    movl   %eax, rPC
    movl   rSELF, %eax
    movl   rPC,OUT_ARG0(%esp)
    movl   %eax,OUT_ARG1(%esp)
    call   dvmJitGetTraceAddrThread  # (pc, self)
    movl   rSELF,%ecx                # ecx <- self
    movl   %eax,offThread_inJitCodeCache(%ecx)  # set inJitCodeCache flag
    cmpl   $$0, %eax
    jz     1f
    jmp    *%eax                     # exec translation if we've got one
    # won't return
1:
    EXPORT_PC
    movl   rSELF, %ecx
    movl   offThread_curHandlerTable(%ecx),rIBASE
    FETCH_INST_R %ecx
    GOTO_NEXT_R %ecx

/*
 * Return from the translation cache and immediately request a
 * translation from the exit target, but don't attempt to chain.
 * rPC set on entry.
 */
    .global dvmJitToInterpTraceSelectNoChain
dvmJitToInterpTraceSelectNoChain:
#if defined(WITH_JIT_TUNING)
    call   dvmBumpNoChain
#endif
    movl   %ebx, rPC
    lea    4(%esp), %esp #to recover the esp update due to function call
    movl   rSELF, %eax
    movl   rPC,OUT_ARG0(%esp)
    movl   %eax,OUT_ARG1(%esp)
    call   dvmJitGetTraceAddrThread  # (pc, self)
    movl   rSELF,%ecx
    cmpl   $$0,%eax
    movl   %eax,offThread_inJitCodeCache(%ecx)  # set inJitCodeCache flag
    jz     1f
    jmp    *%eax              # jump to tranlation
    # won't return

/* No Translation - request one */
1:
    GET_JIT_PROF_TABLE %ecx %eax
    cmpl   $$0, %eax          # JIT enabled?
    jnz    2f                 # Request one if so
    movl   rSELF, %ecx
    movl   offThread_curHandlerTable(%ecx),rIBASE
    FETCH_INST_R %ecx         # Continue interpreting if not
    GOTO_NEXT_R %ecx
2:
    ## Looks like an EXPORT_PC is needed here. Now jmp to common_selectTrace2
    movl   $$kJitTSelectRequestHot,%eax # ask for trace select
    jmp    common_selectTrace

/*
 * Return from the translation cache and immediately request a
 * translation for the exit target.  Reached via a call, and
 * (TOS)->rPC.
 */
    .global dvmJitToInterpTraceSelect
dvmJitToInterpTraceSelect:
    movl   0(%esp), %eax          # get return address
    movl   %ebx, rPC              # get first argument (target rPC)

    ## TODO, need to clean up stack manipulation ... this isn't signal safe and
    ## doesn't use the calling conventions of header.S
    lea    4(%esp), %esp #to recover the esp update due to function call

    ## An additional 5B instruction "jump 0" was added for a thread-safe
    ## chaining cell update in JIT code cache. So the offset is now -17=-12-5.
    lea    -17(%eax), %ebx #$$JIT_OFFSET_CHAIN_START(%eax), %ebx
    lea    -4(%esp), %esp
    movl   rSELF, %eax
    movl   rPC,OUT_ARG0(%esp)
    movl   %eax,OUT_ARG1(%esp)
    call   dvmJitGetTraceAddrThread # (pc, self)
    lea    4(%esp), %esp
    cmpl   $$0,%eax
    movl   rSELF, %ecx
    movl   %eax,offThread_inJitCodeCache(%ecx)  # set inJitCodeCache flag
    jz     1b                 # no - ask for one
    movl   %eax,OUT_ARG0(%esp)
    movl   rINST,OUT_ARG1(%esp)
    call   dvmJitChain        # Attempt dvmJitChain(codeAddr,chainAddr)
    cmpl   $$0,%eax           # Success?
    jz     toInterpreter      # didn't chain - interpret
    jmp    *%eax
    # won't return

/*
 * Placeholder entries for x86 JIT
 */
    .global dvmJitToInterpBackwardBranch
dvmJitToInterpBackwardBranch:

    .global     dvmJitToExceptionThrown
dvmJitToExceptionThrown: //rPC in
    movl   rSELF, %edx
    GET_PC
    movl   $$0, offThread_inJitCodeCache(%edx)
    jmp common_exceptionThrown

    .global dvmJitToInterpNormal
dvmJitToInterpNormal:
/* one input: the target rPC value */
    movl        0(%esp), %eax          # get return address
    movl        %ebx, rPC              # get first argument (target rPC)

    ## TODO, need to clean up stack manipulation ... this isn't signal safe and
    ## doesn't use the calling conventions of header.S

    ## An additional 5B instruction "jump 0" was added for a thread-safe
    ## chaining cell update in JIT code cache. So the offset is now -17=-12-5.
    lea         -17(%eax), %ebx #$$JIT_OFFSET_CHAIN_START(%eax), %ebx
    lea         4(%esp), %esp
    movl        rPC, OUT_ARG0(%esp)
    movl        rSELF, %ecx
    movl        %ecx, OUT_ARG1(%esp)
    call        dvmJitGetTraceAddrThread
    ## Here is the change from using rGLUE to rSELF for accessing the
    ## JIT code cache flag
    movl        rSELF, %ecx
    movl        %eax, offThread_inJitCodeCache(%ecx)  # set inJitCodeCache flag
    #lea         4(%esp), %esp
    cmp         $$0, %eax
    je          toInterpreter
    #lea         -8(%esp), %esp
    movl        %ebx, OUT_ARG1(%esp)    # %ebx live thorugh dvmJitGetTraceAddrThread
    movl        %eax, OUT_ARG0(%esp)    # first argument
    call        dvmJitChain
    #lea         8(%esp), %esp
    cmp         $$0, %eax
    je          toInterpreter
    jmp         *%eax                   #to native address

    .global dvmJitToInterpNoChain
dvmJitToInterpNoChain:
dvmJitToInterpNoChain: #rPC in eax
    ## TODO, need to clean up stack manipulation ... this isn't signal safe and
    ## doesn't use the calling conventions of header.S
    movl        %eax, rPC
    movl        rPC, OUT_ARG0(%esp)
    movl        rSELF, %ecx
    movl        %ecx, OUT_ARG1(%esp)
    call        dvmJitGetTraceAddrThread
    ## Here is the change from using rGLUE to rSELF for accessing the
    ## JIT code cache flag
    movl        rSELF, %ecx
    movl        %eax, offThread_inJitCodeCache(%ecx)  # set inJitCodeCache flag
    cmp         $$0, %eax
    je          toInterpreter
    jmp         *%eax                   #to native address

toInterpreter:
    EXPORT_PC
    movl        rSELF, %ecx
    movl        offThread_curHandlerTable(%ecx), rIBASE
    FETCH_INST
    movl        offThread_pJitProfTable(%ecx), %eax
    #Fallthrough

/* ebx holds the pointer to the jit profile table
   edx has the opCode */
common_testUpdateProfile:
    cmp         $$0, %eax
    je          4f
/* eax holds the pointer to the jit profile table
   edx has the opCode
   rPC points to the next bytecode */

common_updateProfile:
    # quick & dirty hash
    movl   rPC, %ecx
    shrl   $$12, %ecx
    xorl   rPC, %ecx
    andl   $$((1<<JIT_PROF_SIZE_LOG_2)-1), %ecx
    decb   (%ecx,%eax)
    #jmp    1f # remove
    jz     2f
1:
    GOTO_NEXT
2:
common_Profile:
/*
 * Here, we switch to the debug interpreter to request
 * trace selection.  First, though, check to see if there
 * is already a native translation in place (and, if so,
 * jump to it now.
 */
    SPILL(rIBASE)
    SPILL_TMP1(rINST)
    movl        rSELF, rIBASE
    GET_JIT_THRESHOLD rIBASE rINST  # leaves rSELF in %ecx
    EXPORT_PC
    movb   rINSTbl,(%ecx,%eax)   # reset counter
    movl   rIBASE,rINST            # preserve rSELF
    movl   rSELF, %eax
    movl   rPC,OUT_ARG0(%esp)
    movl   rIBASE,OUT_ARG1(%esp)
    call   dvmJitGetTraceAddrThread  # (pc, self)
    UNSPILL(rIBASE)
    movl   %eax,offThread_inJitCodeCache(rINST)   # set the inJitCodeCache flag
    UNSPILL_TMP1(rINST)
    cmpl   $$0,%eax
    #jmp    1f # remove
    jz     1f
    jmp   *%eax        # TODO: decide call vs/ jmp!.  No return either way
1:
    movl   $$kJitTSelectRequest,%eax
    # On entry, eax<- jitState, rPC valid
common_selectTrace:
    mov         %ebx, EBX_SPILL(%ebp)
    movl        rSELF, %ebx
    movzwl      offThread_subMode(%ebx), %ecx
    and         $$(kSubModeJitTraceBuild | kSubModeJitSV), %ecx
    jne         3f                     # already doing JIT work, continue
    movl        %eax, offThread_jitState(%ebx)
    movl        rSELF, %eax
    movl       %eax, OUT_ARG0(%esp)

/*
 * Call out to validate trace-building request. If successful, rIBASE will be swapped
 * to send us into single-steppign trace building mode, so we need to refresh before
 * we continue.
 */

   EXPORT_PC
   SAVE_PC_FP_TO_SELF %ecx
   call dvmJitCheckTraceRequest
3:
   mov          EBX_SPILL(%ebp), %ebx
   FETCH_INST
   movl rSELF, %ecx
   movl offThread_curHandlerTable(%ecx), rIBASE
4:
   GOTO_NEXT

common_selectTrace2:
    mov         %ebx, EBX_SPILL(%ebp)
    movl        rSELF, %ebx
    movl        %ebx, OUT_ARG0(%esp)
    movl        %eax, offThread_jitState(%ebx)
    movzwl      offThread_subMode(%ebx), %ecx
    mov         EBX_SPILL(%ebp), %ebx
    and         (kSubModeJitTraceBuild | kSubModeJitSV), %ecx
    jne         3f                     # already doing JIT work, continue



/*
 * Call out to validate trace-building request. If successful, rIBASE will be swapped
 * to send us into single-steppign trace building mode, so we need to refresh before
 * we continue.
 */

   EXPORT_PC
   SAVE_PC_FP_TO_SELF %ecx
   call dvmJitCheckTraceRequest
3:
   FETCH_INST
   movl rSELF, %ecx
   movl offThread_curHandlerTable(%ecx), rIBASE
4:
   GOTO_NEXT

#endif

/*
 * For the invoke codes we need to know what register holds the "this" pointer. However
 * it seems the this pointer is assigned consistently most times it is in %ecx but other
 * times it is in OP_INVOKE_INTERFACE_JUMBO OP_INVOKE_INTERFACE OP_INVOKE_SUPER_QUICK and
 * OP_INVOKE_VIRTUAL_QUICK
*/

/*
 * Common code for jumbo method invocation.
 *
 * On entry:
 *   eax = Method* methodToCall
 *   rINSTw trashed, must reload
 *   rIBASE trashed, must reload before resuming interpreter
 */

common_invokeMethodJumbo:
.LinvokeNewJumbo:

   /*
    * prepare to copy args to "outs" area of current frame
    */
    movzwl      6(rPC),rINST            # rINST<- BBBB
    movzwl      8(rPC), %ecx            # %ecx<- CCCC
    ADVANCE_PC 2                        # adjust pc to make return similar
    SAVEAREA_FROM_FP %edx               # %edx<- &StackSaveArea
    test        rINST, rINST
    movl        rINST, LOCAL0_OFFSET(%ebp) # LOCAL0_OFFSET(%ebp)<- BBBB
    jz          .LinvokeArgsDone        # no args; jump to args done
    jmp         .LinvokeRangeArgs       # handle args like invoke range

/*
 * Common code for method invocation with range.
 *
 * On entry:
 *   eax = Method* methodToCall
 *   ecx = "this"
 *   rINSTw trashed, must reload
 *   rIBASE trashed, must reload before resuming interpreter
 */

common_invokeMethodRange:
.LinvokeNewRange:
#if defined(WITH_JIT)
    SPILL_TMP1(%edx)
    SPILL_TMP2(%ebx)
    movl        rSELF, %edx
    movzwl      offThread_subMode(%edx), %ebx
    and         $$kSubModeJitTraceBuild, %ebx
    jz          6f
    call        save_callsiteinfo
6:
    UNSPILL_TMP2(%ebx)
    UNSPILL_TMP1(%edx)
#endif
   /*
    * prepare to copy args to "outs" area of current frame
    */

    movzbl      1(rPC),rINST       # rINST<- AA
    movzwl      4(rPC), %ecx            # %ecx<- CCCC
    SAVEAREA_FROM_FP %edx               # %edx<- &StackSaveArea
    test        rINST, rINST
    movl        rINST, LOCAL0_OFFSET(%ebp) # LOCAL0_OFFSET(%ebp)<- AA
    jz          .LinvokeArgsDone        # no args; jump to args done


   /*
    * %eax=methodToCall, %ecx=CCCC, LOCAL0_OFFSET(%ebp)=count,
    * %edx=&outs (&stackSaveArea).  (very few methods have > 10 args;
    * could unroll for common cases)
    */

.LinvokeRangeArgs:
    movl        %ebx, LOCAL1_OFFSET(%ebp)       # LOCAL1_OFFSET(%ebp)<- save %ebx
    lea         (rFP, %ecx, 4), %ecx    # %ecx<- &vCCCC
    shll        $$2, LOCAL0_OFFSET(%ebp)        # LOCAL0_OFFSET(%ebp)<- offset
    subl        LOCAL0_OFFSET(%ebp), %edx       # %edx<- update &outs
    shrl        $$2, LOCAL0_OFFSET(%ebp)        # LOCAL0_OFFSET(%ebp)<- offset
1:
    movl        (%ecx), %ebx            # %ebx<- vCCCC
    lea         4(%ecx), %ecx           # %ecx<- &vCCCC++
    subl        $$1, LOCAL0_OFFSET(%ebp)        # LOCAL0_OFFSET<- LOCAL0_OFFSET--
    movl        %ebx, (%edx)            # *outs<- vCCCC
    lea         4(%edx), %edx           # outs++
    jne         1b                      # loop if count (LOCAL0_OFFSET(%ebp)) not zero
    movl        LOCAL1_OFFSET(%ebp), %ebx       # %ebx<- restore %ebx
    jmp         .LinvokeArgsDone        # continue

   /*
    * %eax is "Method* methodToCall", the method we're trying to call
    * prepare to copy args to "outs" area of current frame
    * rIBASE trashed, must reload before resuming interpreter
    */

common_invokeMethodNoRange:
#if defined(WITH_JIT)
    SPILL_TMP1(%edx)
    SPILL_TMP2(%ebx)
    movl        rSELF, %edx
    movzwl      offThread_subMode(%edx), %ebx
    and         $$kSubModeJitTraceBuild, %ebx
    jz          6f
    call        save_callsiteinfo
6:
    UNSPILL_TMP2(%ebx)
    UNSPILL_TMP1(%edx)
#endif
.LinvokeNewNoRange:
    movzbl      1(rPC),rINST       # rINST<- BA
    movl        rINST, LOCAL0_OFFSET(%ebp) # LOCAL0_OFFSET(%ebp)<- BA
    shrl        $$4, LOCAL0_OFFSET(%ebp)        # LOCAL0_OFFSET(%ebp)<- B
    je          .LinvokeArgsDone        # no args; jump to args done
    movzwl      4(rPC), %ecx            # %ecx<- GFED
    SAVEAREA_FROM_FP %edx               # %edx<- &StackSaveArea

   /*
    * %eax=methodToCall, %ecx=GFED, LOCAL0_OFFSET(%ebp)=count, %edx=outs
    */

.LinvokeNonRange:
    cmp         $$2, LOCAL0_OFFSET(%ebp)        # compare LOCAL0_OFFSET(%ebp) to 2
    movl        %ecx, LOCAL1_OFFSET(%ebp)       # LOCAL1_OFFSET(%ebp)<- GFED
    jl          1f                      # handle 1 arg
    je          2f                      # handle 2 args
    cmp         $$4, LOCAL0_OFFSET(%ebp)        # compare LOCAL0_OFFSET(%ebp) to 4
    jl          3f                      # handle 3 args
    je          4f                      # handle 4 args
5:
    andl        $$15, rINST             # rINSTw<- A
    lea         -4(%edx), %edx          # %edx<- update &outs; &outs--
    movl        (rFP, rINST, 4), %ecx   # %ecx<- vA
    movl        %ecx, (%edx)            # *outs<- vA
    movl        LOCAL1_OFFSET(%ebp), %ecx       # %ecx<- GFED
4:
    shr         $$12, %ecx              # %ecx<- G
    lea         -4(%edx), %edx          # %edx<- update &outs; &outs--
    movl        (rFP, %ecx, 4), %ecx    # %ecx<- vG
    movl        %ecx, (%edx)            # *outs<- vG
    movl        LOCAL1_OFFSET(%ebp), %ecx       # %ecx<- GFED
3:
    and         $$0x0f00, %ecx          # %ecx<- 0F00
    shr         $$8, %ecx               # %ecx<- F
    lea         -4(%edx), %edx          # %edx<- update &outs; &outs--
    movl        (rFP, %ecx, 4), %ecx    # %ecx<- vF
    movl        %ecx, (%edx)            # *outs<- vF
    movl        LOCAL1_OFFSET(%ebp), %ecx       # %ecx<- GFED
2:
    and         $$0x00f0, %ecx          # %ecx<- 00E0
    shr         $$4, %ecx               # %ecx<- E
    lea         -4(%edx), %edx          # %edx<- update &outs; &outs--
    movl        (rFP, %ecx, 4), %ecx    # %ecx<- vE
    movl        %ecx, (%edx)            # *outs<- vE
    movl        LOCAL1_OFFSET(%ebp), %ecx       # %ecx<- GFED
1:
    and         $$0x000f, %ecx          # %ecx<- 000D
    movl        (rFP, %ecx, 4), %ecx    # %ecx<- vD
    movl        %ecx, -4(%edx)          # *--outs<- vD
0:

   /*
    * %eax is "Method* methodToCall", the method we're trying to call
    * find space for the new stack frame, check for overflow
    */

.LinvokeArgsDone:
    movzwl      offMethod_registersSize(%eax), %edx # %edx<- methodToCall->regsSize
    movzwl      offMethod_outsSize(%eax), %ecx # %ecx<- methodToCall->outsSize
    movl        %eax, LOCAL0_OFFSET(%ebp)       # LOCAL0_OFFSET<- methodToCall
    shl         $$2, %edx               # %edx<- update offset
    SAVEAREA_FROM_FP %eax               # %eax<- &StackSaveArea
    subl        %edx, %eax              # %eax<- newFP; (old savearea - regsSize)
    movl        rSELF,%edx              # %edx<- pthread
    movl        %eax, LOCAL1_OFFSET(%ebp)       # LOCAL1_OFFSET(%ebp)<- &outs
    subl        $$sizeofStackSaveArea, %eax # %eax<- newSaveArea (stack save area using newFP)
    movl        offThread_interpStackEnd(%edx), %edx # %edx<- self->interpStackEnd
    movl        %edx, TMP_SPILL1(%ebp)  # spill self->interpStackEnd
    shl         $$2, %ecx               # %ecx<- update offset for outsSize
    movl        %eax, %edx              # %edx<- newSaveArea
    sub         %ecx, %eax              # %eax<- bottom; (newSaveArea - outsSize)
    cmp         TMP_SPILL1(%ebp), %eax  # compare interpStackEnd and bottom
    movl        LOCAL0_OFFSET(%ebp), %eax       # %eax<- restore methodToCall
    jl          .LstackOverflow         # handle frame overflow

   /*
    * set up newSaveArea
    */

#ifdef EASY_GDB
    SAVEAREA_FROM_FP %ecx               # %ecx<- &StackSaveArea
    movl        %ecx, offStackSaveArea_prevSave(%edx) # newSaveArea->prevSave<- &outs
#endif
    movl        rSELF,%ecx              # %ecx<- pthread
    movl        rFP, offStackSaveArea_prevFrame(%edx) # newSaveArea->prevFrame<- rFP
    movl        rPC, offStackSaveArea_savedPc(%edx) # newSaveArea->savedPc<- rPC
#if defined(WITH_JIT)
    movl        $$0, offStackSaveArea_returnAddr(%edx)
#endif

    /* Any special actions to take? */
    cmpw        $$0, offThread_subMode(%ecx)
    jne         2f                     # Yes - handle them
1:
    testl       $$ACC_NATIVE, offMethod_accessFlags(%eax) # check for native call
    movl        %eax, offStackSaveArea_method(%edx) # newSaveArea->method<- method to call
    jne         .LinvokeNative          # handle native call

   /*
    * Update "self" values for the new method
    * %eax=methodToCall, LOCAL1_OFFSET(%ebp)=newFp
    */
    movl        offMethod_clazz(%eax), %edx # %edx<- method->clazz
    movl        offClassObject_pDvmDex(%edx), %edx # %edx<- method->clazz->pDvmDex
    movl        %eax, offThread_method(%ecx) # self->method<- methodToCall
    movl        %edx, offThread_methodClassDex(%ecx) # self->methodClassDex<- method->clazz->pDvmDex
    movl        offMethod_insns(%eax), rPC # rPC<- methodToCall->insns
    movl        $$1, offThread_debugIsMethodEntry(%ecx)
    movl        LOCAL1_OFFSET(%ebp), rFP # rFP<- newFP
    movl        rFP, offThread_curFrame(%ecx) # curFrame<-newFP
    movl        offThread_curHandlerTable(%ecx),rIBASE
    FETCH_INST
#if defined(WITH_JIT)
    /* rPC is already updated */
    GET_JIT_PROF_TABLE %ecx %eax
    cmp         $$0, %eax
    jne         common_updateProfile # set up %ebx & %edx & rPC
#endif
    GOTO_NEXT                           # jump to methodToCall->insns

2:
    /*
     * On entry, preserve all:
     *  %eax: method
     *  %ecx: self
     *  %edx: new save area
     */
    SPILL_TMP1(%eax)                   # preserve methodToCall
    SPILL_TMP2(%edx)                   # preserve newSaveArea
    movl        rPC, offThread_pc(%ecx) # update interpSave.pc
    movl        %ecx, OUT_ARG0(%esp)
    movl        %eax, OUT_ARG1(%esp)
    call        dvmReportInvoke        # (self, method)
    UNSPILL_TMP1(%eax)
    UNSPILL_TMP2(%edx)
    movl        rSELF,%ecx             # restore rSELF
    jmp         1b

   /*
    * Prep for the native call
    * %eax=methodToCall, LOCAL1_OFFSET(%ebp)=newFP, %edx=newSaveArea, %ecx=self
    */

.LinvokeNative:
    movl        offThread_jniLocal_topCookie(%ecx), rINST # rINST<- self->localRef->...
    movl        rINST, offStackSaveArea_localRefCookie(%edx) # newSaveArea->localRefCookie<- top
    movl        %edx, LOCAL2_OFFSET(%ebp)  # save newSaveArea
    movl        LOCAL1_OFFSET(%ebp), rINST # rINST<- newFP
    movl        rINST, offThread_curFrame(%ecx)  # curFrame<- newFP
    cmpw        $$0, offThread_subMode(%ecx)  # Anything special going on?
    jne         11f                     # yes - handle it
    movl        %ecx, OUT_ARG3(%esp)    # push parameter self
    movl        %eax, OUT_ARG2(%esp)    # push parameter methodToCall
    lea         offThread_retval(%ecx), %ecx # %ecx<- &retval
    movl        %ecx, OUT_ARG1(%esp)    # push parameter &retval
    movl        rINST, OUT_ARG0(%esp)    # push parameter newFP
    call        *offMethod_nativeFunc(%eax) # call methodToCall->nativeFunc
7:
    movl        LOCAL2_OFFSET(%ebp), %ecx    # %ecx<- newSaveArea
    movl        rSELF, %eax             # %eax<- self
    movl        offStackSaveArea_localRefCookie(%ecx), %edx # %edx<- old top
    cmp         $$0, offThread_exception(%eax) # check for exception
    movl        rFP, offThread_curFrame(%eax) # curFrame<- rFP
    movl        %edx, offThread_jniLocal_topCookie(%eax) # new top <- old top
    jne         common_exceptionThrown  # handle exception
    movl        offThread_curHandlerTable(%eax),rIBASE
    FETCH_INST_OPCODE 3 %ecx
    ADVANCE_PC 3
    GOTO_NEXT_R %ecx                    # jump to next instruction

11:
    /*
     * Handle any special subMode actions
     * %eax=methodToCall, rINST=newFP, %ecx=self
     */
    SPILL_TMP1(%eax)                    # save methodTocall
    movl        rPC, offThread_pc(%ecx)
    movl        %ecx, OUT_ARG1(%esp)
    movl        %eax, OUT_ARG0(%esp)
    movl        rFP, OUT_ARG2(%esp)
    call        dvmReportPreNativeInvoke # (methodToCall, self, fp)
    UNSPILL_TMP1(%eax)                  # restore methodToCall
    movl        rSELF,%ecx              # restore self

    /* Do the native call */
    movl        %ecx, OUT_ARG3(%esp)    # push parameter self
    lea         offThread_retval(%ecx), %ecx # %ecx<- &retval
    movl        %eax, OUT_ARG2(%esp)    # push parameter methodToCall
    movl        %ecx, OUT_ARG1(%esp)    # push parameter &retval
    movl        rINST, OUT_ARG0(%esp)   # push parameter newFP
    call        *offMethod_nativeFunc(%eax) # call methodToCall->nativeFunc

    UNSPILL_TMP1(%eax)                  # restore methodToCall
    movl        rSELF, %ecx
    movl        %ecx, OUT_ARG1(%esp)
    movl        %eax, OUT_ARG0(%esp)
    movl        rFP, OUT_ARG2(%esp)
    call        dvmReportPostNativeInvoke # (methodToCall, self, fp)
    jmp         7b                      # rejoin

.LstackOverflow:    # eax=methodToCall
    movl        %eax, OUT_ARG1(%esp)    # push parameter methodToCall
    movl        rSELF,%eax              # %eax<- self
    movl        %eax, OUT_ARG0(%esp)    # push parameter self
    call        dvmHandleStackOverflow  # call: (Thread* self, Method* meth)
    jmp         common_exceptionThrown  # handle exception


/*
 * Common code for handling a return instruction
 */
common_returnFromMethod:
    movl    rSELF, %ecx
    SAVEAREA_FROM_FP %eax                       # %eax<- saveArea(old)
    cmpw    $$0, offThread_subMode(%ecx)          # special action needed?
    jne     19f                                   # go if so
14:

    movl        offStackSaveArea_prevFrame(%eax), rFP # rFP<- saveArea->PrevFrame
    movl        (offStackSaveArea_method - sizeofStackSaveArea)(rFP), rINST # rINST<- method we are returning to
    cmpl        $$0, rINST               # check for break frame
    je          common_gotoBail         # bail if break frame
    movl        offThread_curHandlerTable(%ecx),rIBASE
    movl        offStackSaveArea_savedPc(%eax), rPC # rPC<- saveAreaOld->savedPc
#if defined(WITH_JIT)
    movl        offStackSaveArea_returnAddr(%eax), %ecx
#endif
    movl        rSELF, %eax
    movl        rINST, offThread_method(%eax) # glue->method<- newSave->method
    movl        offMethod_clazz(rINST), rINST # rINST<- method->clazz
    movl        rFP, offThread_curFrame(%eax) # glue->self->curFrame<- rFP
#if defined(WITH_JIT)
    //update self->offThread_inJitCodeCache
    movl        %ecx, offThread_inJitCodeCache(%eax)
#endif
    movl        offClassObject_pDvmDex(rINST), rINST # rINST<- method->clazz->pDvmDex
    movl        rINST, offThread_methodClassDex(%eax) # glue->pDvmDex<- method->clazz->pDvmDex
#if defined(WITH_JIT)
    cmp         $$0, %ecx
    je          .returnToBC
    movl        %ecx, %eax
    jmp         *%eax
#endif

.returnToBC:

#if defined(WITH_JIT)
    FETCH_INST_OPCODE  3, %ecx                 # %eax<- next instruction hi; fetch, advance
    // %ecx has the opcode
    addl         $$6, rPC               # 3*2 = 6
    SPILL_TMP1   (%ecx)
    movl         rSELF, %ecx
    FETCH_INST
    UNSPILL_TMP1   (%ecx)
    movzbl      1(rPC), rINST
    jmp     *(rIBASE,%ecx,4)
#else
    FETCH_INST_WORD 3
    ADVANCE_PC 3
    GOTO_NEXT
#endif

19:
    /*
     * Handle special subMode actions
     * On entry, rFP: prevFP, %ecx: self, %eax: saveArea
     */
    SPILL_TMP1(%ebx)
    movl     offStackSaveArea_prevFrame(%eax), %ebx # %ebx<- saveArea->PrevFrame
    movl     rPC, offThread_pc(%ecx)          # update interpSave.pc
    movl     %ebx, offThread_curFrame(%ecx)    # update interpSave.curFrame
    movl     %ecx, OUT_ARG0(%esp)             # parameter self
    call     dvmReportReturn                  # (self)
    UNSPILL_TMP1(%ebx)
    movl     rSELF, %ecx                      # restore self
    SAVEAREA_FROM_FP %eax                     # restore saveArea
    jmp      14b


/*
 * Prepare to strip the current frame and "longjump" back to caller of
 * dvmMterpStdRun.
 *
 * on entry:
 *    rINST holds changeInterp
 *    ecx holds self pointer
 *
 * expected profile: dvmMterpStdBail(Thread *self, bool changeInterp)
 */
common_gotoBail:
    movl   rPC,offThread_pc(%ecx)     # export state to self
    movl   rFP,offThread_curFrame(%ecx)
    movl   %ecx,OUT_ARG0(%esp)      # self in arg0
    movl   rINST,OUT_ARG1(%esp)     # changeInterp in arg1
    call   dvmMterpStdBail          # bail out....

/*
 * The JIT's invoke method needs to remember the callsite class and
 * target pair.  Save them here so that they are available to
 * dvmCheckJit following the interpretation of this invoke.
 *
 * eax = Method* methodToCall
 * ecx = "this"
 * edx = rSELF
 * ebx = free to use
 */
#if defined(WITH_JIT)
save_callsiteinfo:
    cmp     $$0, %ecx
    je      2f
    movl    offObject_clazz(%ecx), %ecx
2:
    movl    rSELF, %ebx
    movl    %eax, offThread_methodToCall(%ebx)
    movl    %ecx, offThread_callsiteClass(%ebx)
    ret
#endif

#if defined(WITH_JIT)

    /*
     * If the JIT is actively building a trace we need to make sure
     * that the field is fully resolved before including the current
     * instruction.
     *
     * On entry:
     *     %ecx: &dvmDex->pResFields[field]
     *     %eax:  field pointer (must preserve)
     */
common_verifyField:
    movl    %ebx, TMP_SPILL1(%ebp)
    movl     rSELF, %ebx
    movzwl   offThread_subMode(%ebx), %ebx
    andl     $$kSubModeJitTraceBuild, %ebx
    movl    TMP_SPILL1(%ebp), %ebx
    jne      1f
    ret
1:
    movl    (%ecx), %ecx
    cmp     $$0, %ecx
    je      1f
    ret
1:
    SPILL_TMP1(%eax)
    SPILL_TMP2(%edx)
    movl     rSELF, %ecx
    # Because we call into this helper from a bytecode, we have
    # to be careful not to write over the return address when using
    # the OUT_ARG macros
    lea      -8(%esp), %esp
    movl     %ecx, OUT_ARG0(%esp)
    movl     rPC, OUT_ARG1(%esp)
    call     dvmJitEndTraceSelect
    lea      8(%esp), %esp
    UNSPILL_TMP2(%edx)
    UNSPILL_TMP1(%eax)
    ret
#endif

/*
 * After returning from a "selfd" function, pull out the updated values
 * and start executing at the next instruction.
 */
common_resumeAfterGlueCall:
     movl  rSELF, %eax
     movl  offThread_pc(%eax),rPC
     movl  offThread_curFrame(%eax),rFP
     movl  offThread_curHandlerTable(%eax),rIBASE
     FETCH_INST
     GOTO_NEXT

/*
 * Integer divide or mod by zero
 */
common_errDivideByZero:
    EXPORT_PC
    movl    $$.LstrDivideByZero,%eax
    movl    %eax,OUT_ARG0(%esp)
    call    dvmThrowArithmeticException
    jmp     common_exceptionThrown

/*
 * Attempt to allocate an array with a negative size.
 * On entry, len in eax
 */
common_errNegativeArraySize:
    EXPORT_PC
    movl    %eax,OUT_ARG0(%esp)                  # arg0<- len
    call    dvmThrowNegativeArraySizeException   # (len)
    jmp     common_exceptionThrown

/*
 * Attempt to allocate an array with a negative size.
 * On entry, method name in eax
 */
common_errNoSuchMethod:
    EXPORT_PC
    movl    %eax,OUT_ARG0(%esp)
    call    dvmThrowNoSuchMethodError
    jmp     common_exceptionThrown

/*
 * Hit a null object when we weren't expecting one.  Export the PC, throw a
 * NullPointerException and goto the exception processing code.
 */
common_errNullObject:
    EXPORT_PC
    xorl    %eax,%eax
    movl    %eax,OUT_ARG0(%esp)
    call    dvmThrowNullPointerException
    jmp     common_exceptionThrown

/*
 * Array index exceeds max.
 * On entry:
 *    eax <- array object
 *    ecx <- index
 */
common_errArrayIndex:
    EXPORT_PC
    movl    offArrayObject_length(%eax), %eax
    movl    %eax,OUT_ARG0(%esp)
    movl    %ecx,OUT_ARG1(%esp)
    call    dvmThrowArrayIndexOutOfBoundsException   # args (length, index)
    jmp     common_exceptionThrown

/*
 * Somebody has thrown an exception.  Handle it.
 *
 * If the exception processing code returns to us (instead of falling
 * out of the interpreter), continue with whatever the next instruction
 * now happens to be.
 *
 * NOTE: special subMode handling done in dvmMterp_exceptionThrown
 *
 * This does not return.
 */
common_exceptionThrown:
.LexceptionNew:

    EXPORT_PC
    movl       rSELF, %ecx
    movl       %ecx, OUT_ARG0(%esp)
    call       dvmCheckSuspendPending

    movl       rSELF, %ecx
    movl       offThread_exception(%ecx), %edx   # %edx <- self->exception
    movl       %edx, OUT_ARG0(%esp)
    movl       %ecx, OUT_ARG1(%esp)
    SPILL_TMP1(%edx)
    call       dvmAddTrackedAlloc      # don't let the exception be GCed
    UNSPILL_TMP1(%edx)
    movl       rSELF, %ecx
    movl       offThread_subMode(%ecx), %eax    # get subMode flags
    movl       $$0, offThread_exception(%ecx)

    # Special subMode?
    cmpl       $$0, %eax                # any special subMode handling needed?
    je         8f                      # go if so

    # Manage debugger bookkeeping
    movl       rPC, offThread_pc(%ecx) # update interpSave.pc
    movl       rFP, offThread_curFrame(%ecx) # update interpSave.curFrame
    movl       %ecx, OUT_ARG0(%esp)
    movl       %edx, OUT_ARG1(%esp)
    SPILL_TMP1(%edx)
    call       dvmReportExceptionThrow # (self, exception)
    UNSPILL_TMP1(%edx)
    movl       rSELF, %ecx

8:
    /*
    * set up args and a local for &fp
    */
    lea        20(%esp), %esp          # raise %esp
    movl       rFP, (%esp)               # save fp
    movl       %esp, %eax              # %eax = &fp
    lea        -20(%esp), %esp         # reset %esp
    movl       %eax, OUT_ARG4(%esp)    # Arg 4 = &fp
    movl       $$0, OUT_ARG3(%esp)      # Arg 3 = false
    movl       %edx, OUT_ARG2(%esp)    # Arg 2 = exception
    movl       %ecx, OUT_ARG0(%esp)    # Arg 0 = self

    movl       offThread_method(%ecx), %eax # %eax = self->method
    movl       offMethod_insns(%eax), %eax  # %eax = self->method->insn
    # ldrh    lr, [rSELF, #offThread_subMode]  @ lr<- subMode flags  # TODO
    movl       rPC, %ecx
    subl       %eax, %ecx              # %ecx = pc - self->method->insn
    sar        $$1, %ecx                # adjust %ecx for code offset
    movl       %ecx, OUT_ARG1(%esp)    # Arg 1 = %ecx

    /* call, %eax gets catchRelPc (a code-unit offset) */
    SPILL_TMP1(%edx)                   # save exception
    call       dvmFindCatchBlock       # call(self, relPc, exc, scan?, &fp)
    UNSPILL_TMP1(%edx)                 # restore exception

    /* fix earlier stack overflow if necessary; may trash rFP */
    movl       rSELF, %ecx
    cmpl       $$0, offThread_stackOverflowed(%ecx) # did we overflow?
    je         1f                         # no, skip ahead
    movl       %eax, rFP                  # save relPc result in rFP
    movl       %ecx, OUT_ARG0(%esp)       # Arg 0 = self
    movl       %edx, OUT_ARG1(%esp)       # Arg 1 = exception
    SPILL_TMP1(%edx)
    call       dvmCleanupStackOverflow    # call(self, exception)
    UNSPILL_TMP1(%edx)
    movl       rFP, %eax                  # restore result
    movl       rSELF, %ecx
1:

    /* update frame pointer and check result from dvmFindCatchBlock */
    movl       20(%esp), rFP              # retrieve the updated rFP
    cmpl       $$0, %eax                  # is catchRelPc < 0?
    jl         .LnotCaughtLocally

    /* adjust locals to match self->interpSave.curFrame and updated PC */
    SAVEAREA_FROM_FP rINST             # rINST<- new save area
    movl       offStackSaveArea_method(rINST), rINST # rINST<- new method
    movl       rINST, offThread_method(%ecx)         # self->method = new method
    movl       offMethod_clazz(rINST), %ecx          # %ecx = method->clazz
    movl       offMethod_insns(rINST), rINST         # rINST = method->insn
    movl       offClassObject_pDvmDex(%ecx), %ecx    # %ecx = method->clazz->pDvmDex
    lea        (rINST, %eax, 2), rPC      # rPC<- method->insns + catchRelPc
    movl       rSELF, rINST
    movl       %ecx, offThread_methodClassDex(rINST) # self->pDvmDex = method->clazz->pDvmDex

    /* release the tracked alloc on the exception */
    movl       %edx, OUT_ARG0(%esp)       # Arg 0 = exception
    movl       rINST, OUT_ARG1(%esp)      # Arg 1 = self
    SPILL_TMP1(%edx)
    call       dvmReleaseTrackedAlloc     # release the exception
    UNSPILL_TMP1(%edx)

    /* restore the exception if the handler wants it */
    movl       rSELF, %ecx
    FETCH_INST
    movzbl     rINSTbl, %eax
    cmpl       $$OP_MOVE_EXCEPTION, %eax   # is it "move-exception"?
    jne        1f
    movl       %edx, offThread_exception(%ecx) # restore exception
1:
    movl       offThread_curHandlerTable(%ecx), rIBASE # refresh rIBASE
    GOTO_NEXT

.LnotCaughtLocally: # %edx = exception
    /* fix stack overflow if necessary */
    movl       rSELF, %ecx
    movl       offThread_stackOverflowed(%ecx), %eax
    cmpl       $$0, %eax                   # did we overflow earlier?
    je         1f
    movl       %ecx, OUT_ARG0(%esp)
    movl       %edx, OUT_ARG1(%esp)
    SPILL_TMP1(%edx)
    call       dvmCleanupStackOverflow
    UNSPILL_TMP1(%edx)

1:
    movl       rSELF, %ecx
    movl       %edx, offThread_exception(%ecx) #restore exception
    movl       %edx, OUT_ARG0(%esp)
    movl       %ecx, OUT_ARG1(%esp)
    call       dvmReleaseTrackedAlloc     # release the exception
    movl       rSELF, %ecx
    jmp        common_gotoBail            # bail out

common_abort:
    movl    $$0xdeadf00d,%eax
    call     *%eax


/*
 * Strings
 */

    .section     .rodata
.LstrDivideByZero:
    .asciz  "divide by zero"
.LstrFilledNewArrayNotImplA:
    .asciz  "filled-new-array only implemented for 'int'"
