%def header(): /* * Copyright (C) 2020 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. */ /* * This is a #include, not a %include, because we want the C pre-processor * to expand the macros into assembler assignment statements. */ #include "asm_support.h" #include "arch/arm/asm_support_arm.S" /** * ARM EABI general notes: * * r0-r3 hold first 4 args to a method; they are not preserved across method calls * r4-r8 are available for general use * r9 is given special treatment in some situations, but not for us * r10 (sl) seems to be generally available * r11 (fp) is used by gcc (unless -fomit-frame-pointer is set) * r12 (ip) is scratch -- not preserved across method calls * r13 (sp) should be managed carefully in case a signal arrives * r14 (lr) must be preserved * r15 (pc) can be tinkered with directly * * r0 holds returns of <= 4 bytes * r0-r1 hold returns of 8 bytes, low word in r0 * * Callee must save/restore r4+ (except r12) if it modifies them. If VFP * is present, registers s16-s31 (a/k/a d8-d15, a/k/a q4-q7) must be preserved, * s0-s15 (d0-d7, q0-a3) do not need to be. * * Stack is "full descending". Only the arguments that don't fit in the first 4 * registers are placed on the stack. "sp" points at the first stacked argument * (i.e. the 5th arg). * * Native ABI uses soft-float, single-precision results are in r0, * double-precision results in r0-r1. * * In the EABI, "sp" must be 64-bit aligned on entry to a function, and any * 64-bit quantities (long long, double) must be 64-bit aligned. * * Nterp notes: * * The following registers have fixed assignments: * * reg nick purpose * r5 rFP interpreted frame pointer, used for accessing locals and args * r6 rREFS base of object references of dex registers * r7 rINST first 16-bit code unit of current instruction * r8 rMR marking register * r9 rSELF self (Thread) pointer * r10 rIBASE interpreted instruction base pointer, used for computed goto * r11 rPC interpreted program counter, used for fetching instructions * * r4, ip, and lr can be used as temporary * * Note that r4 is a callee-save register in ARM EABI, but not in managed code. * */ /* single-purpose registers, given names for clarity */ #define CFI_DEX 11 // DWARF register number of the register holding dex-pc (rPC). #define CFI_TMP 0 // DWARF register number of the first argument register (r0). #define CFI_REFS 6 #define rFP r5 #define rREFS r6 #define rINST r7 #define rSELF r9 #define rIBASE r10 #define rPC r11 // To avoid putting ifdefs arond the use of rMR, make sure it's defined. // IsNterpSupported returns false for configurations that don't have rMR (typically CMS). #ifndef rMR #define rMR r8 #endif // Temporary registers while setting up a frame. #define rNEW_FP r8 #define rNEW_REFS r10 #define CFI_NEW_REFS 10 #define CALLEE_SAVES_SIZE (9 * 4 + 16 * 4) // +4 for the ArtMethod of the caller. #define OFFSET_TO_FIRST_ARGUMENT_IN_STACK (CALLEE_SAVES_SIZE + 4) /* * Fetch the next instruction from rPC into rINST. Does not advance rPC. */ .macro FETCH_INST ldrh rINST, [rPC] .endm /* * Fetch the next instruction from the specified offset. Advances rPC * to point to the next instruction. "count" is in 16-bit code units. * * Because of the limited size of immediate constants on ARM, this is only * suitable for small forward movements (i.e. don't try to implement "goto" * with this). * * This must come AFTER anything that can throw an exception, or the * exception catch may miss. (This also implies that it must come after * EXPORT_PC.) */ .macro FETCH_ADVANCE_INST count ldrh rINST, [rPC, #((\count)*2)]! .endm /* * Similar to FETCH_ADVANCE_INST, but does not update xPC. Used to load * rINST ahead of possible exception point. Be sure to manually advance xPC * later. */ .macro PREFETCH_INST count ldrh rINST, [rPC, #((\count)*2)] .endm /* Advance xPC by some number of code units. */ .macro ADVANCE count add rPC, #((\count)*2) .endm /* * Fetch the next instruction from an offset specified by "reg" and advance xPC. * xPC to point to the next instruction. "reg" must specify the distance * in bytes, *not* 16-bit code units, and may be a signed value. */ .macro FETCH_ADVANCE_INST_RB reg ldrh rINST, [rPC, \reg]! .endm /* * Fetch a half-word code unit from an offset past the current PC. The * "count" value is in 16-bit code units. Does not advance xPC. * * The "_S" variant works the same but treats the value as signed. */ .macro FETCH reg, count ldrh \reg, [rPC, #((\count)*2)] .endm .macro FETCH_S reg, count ldrsh \reg, [rPC, #((\count)*2)] .endm /* * Fetch one byte from an offset past the current PC. Pass in the same * "count" as you would for FETCH, and an additional 0/1 indicating which * byte of the halfword you want (lo/hi). */ .macro FETCH_B reg, count, byte ldrb \reg, [rPC, #((\count)*2+(\byte))] .endm /* * Put the instruction's opcode field into the specified register. */ .macro GET_INST_OPCODE reg and \reg, rINST, #255 .endm /* * Begin executing the opcode in _reg. Clobbers reg */ .macro GOTO_OPCODE reg add pc, rIBASE, \reg, lsl #${handler_size_bits} .endm /* * Get/set value from a Dalvik register. */ .macro GET_VREG reg, vreg ldr \reg, [rFP, \vreg, lsl #2] .endm .macro GET_VREG_OBJECT reg, vreg ldr \reg, [rREFS, \vreg, lsl #2] .endm .macro SET_VREG reg, vreg str \reg, [rFP, \vreg, lsl #2] mov \reg, #0 str \reg, [rREFS, \vreg, lsl #2] .endm .macro SET_VREG_OBJECT reg, vreg str \reg, [rFP, \vreg, lsl #2] str \reg, [rREFS, \vreg, lsl #2] .endm .macro SET_VREG_FLOAT reg, vreg, tmpreg add \tmpreg, rFP, \vreg, lsl #2 vstr \reg, [\tmpreg] mov \tmpreg, #0 str \tmpreg, [rREFS, \vreg, lsl #2] .endm .macro GET_VREG_WIDE_BY_ADDR reg0, reg1, addr ldmia \addr, {\reg0, \reg1} .endm .macro SET_VREG_WIDE_BY_ADDR reg0, reg1, addr stmia \addr, {\reg0, \reg1} .endm .macro GET_VREG_FLOAT sreg, vreg ldr \vreg, [rFP, \vreg, lsl #2] vmov \sreg, \vreg .endm .macro GET_VREG_FLOAT_BY_ADDR reg, addr vldr \reg, [\addr] .endm .macro SET_VREG_FLOAT_BY_ADDR reg, addr vstr \reg, [\addr] .endm .macro GET_VREG_DOUBLE_BY_ADDR reg, addr vldr \reg, [\addr] .endm .macro SET_VREG_DOUBLE_BY_ADDR reg, addr vstr \reg, [\addr] .endm .macro SET_VREG_SHADOW reg, vreg str \reg, [rREFS, \vreg, lsl #2] .endm .macro CLEAR_SHADOW_PAIR vreg, tmp1, tmp2 mov \tmp1, #0 add \tmp2, \vreg, #1 SET_VREG_SHADOW \tmp1, \vreg SET_VREG_SHADOW \tmp1, \tmp2 .endm .macro VREG_INDEX_TO_ADDR reg, vreg add \reg, rFP, \vreg, lsl #2 .endm // An assembly entry that has a OatQuickMethodHeader prefix. .macro OAT_ENTRY name, end .arm .type \name, #function .hidden \name .global \name .balign 16 // Padding of 3 * 8 bytes to get 16 bytes alignment of code entry. .long 0 .long 0 .long 0 // OatQuickMethodHeader. Note that the top two bits must be clear. .long (\end - \name) \name: .endm .macro SIZE name .size \name, .-\name .endm .macro NAME_START name .arm .type \name, #function .hidden \name // Hide this as a global symbol, so we do not incur plt calls. .global \name /* Cache alignment for function entry */ .balign 16 \name: .endm .macro NAME_END name SIZE \name .endm // Macro for defining entrypoints into runtime. We don't need to save registers // (we're not holding references there), but there is no // kDontSave runtime method. So just use the kSaveRefsOnly runtime method. .macro NTERP_TRAMPOLINE name, helper ENTRY \name SETUP_SAVE_REFS_ONLY_FRAME ip bl \helper RESTORE_SAVE_REFS_ONLY_FRAME REFRESH_MARKING_REGISTER RETURN_OR_DELIVER_PENDING_EXCEPTION END \name .endm .macro CLEAR_STATIC_VOLATILE_MARKER reg and \reg, \reg, #-2 .endm .macro CLEAR_INSTANCE_VOLATILE_MARKER reg rsb \reg, \reg, #0 .endm .macro EXPORT_PC str rPC, [rREFS, #-8] .endm .macro BRANCH // Update method counter and do a suspend check if the branch is negative. cmp rINST, #0 blt 2f 1: add r2, rINST, rINST // r2<- byte offset FETCH_ADVANCE_INST_RB r2 // update xPC, load rINST GET_INST_OPCODE ip // extract opcode from rINST GOTO_OPCODE ip // jump to next instruction 2: ldr r0, [sp] ldrh r2, [r0, #ART_METHOD_HOTNESS_COUNT_OFFSET] add r2, r2, #1 ubfx r2, r2, #0, #NTERP_HOTNESS_BITS strh r2, [r0, #ART_METHOD_HOTNESS_COUNT_OFFSET] // If the counter overflows, handle this in the runtime. cmp r2, #0 beq NterpHandleHotnessOverflow // Otherwise, do a suspend check. ldr r0, [rSELF, #THREAD_FLAGS_OFFSET] ands r0, r0, #THREAD_SUSPEND_OR_CHECKPOINT_REQUEST beq 1b EXPORT_PC bl art_quick_test_suspend b 1b .endm // Expects: // - ip and lr to be available. // Outputs: // - \registers contains the dex registers size // - \outs contains the outs size // - if load_ins is 1, \ins contains the ins // - \code_item is replaced with a pointer to the instructions .macro FETCH_CODE_ITEM_INFO code_item, registers, outs, ins, load_ins tst \code_item, #1 beq 5f bic \code_item, \code_item, #1 // Remove the extra bit that marks it's a compact dex file ldrh lr, [\code_item, #COMPACT_CODE_ITEM_FIELDS_OFFSET] ubfx \registers, lr, #COMPACT_CODE_ITEM_REGISTERS_SIZE_SHIFT, #4 ubfx \outs, lr, #COMPACT_CODE_ITEM_OUTS_SIZE_SHIFT, #4 .if \load_ins ubfx \ins, lr, #COMPACT_CODE_ITEM_INS_SIZE_SHIFT, #4 .else ubfx ip, lr, #COMPACT_CODE_ITEM_INS_SIZE_SHIFT, #4 add \registers, \registers, ip .endif ldrh lr, [\code_item, #COMPACT_CODE_ITEM_FLAGS_OFFSET] tst lr, #COMPACT_CODE_ITEM_REGISTERS_INS_OUTS_FLAGS beq 4f mov ip, \code_item tst lr, #COMPACT_CODE_ITEM_INSNS_FLAG beq 1f sub ip, ip, #4 1: tst lr, #COMPACT_CODE_ITEM_REGISTERS_FLAG beq 2f ldrh lr, [ip, #-2]! add \registers, \registers, lr ldrh lr, [\code_item, #COMPACT_CODE_ITEM_FLAGS_OFFSET] 2: tst lr, #COMPACT_CODE_ITEM_INS_FLAG beq 3f ldrh lr, [ip, #-2]! .if \load_ins add \ins, \ins, lr .else add \registers, \registers, lr .endif ldrh lr, [\code_item, #COMPACT_CODE_ITEM_FLAGS_OFFSET] 3: tst lr, #COMPACT_CODE_ITEM_OUTS_FLAG beq 4f ldrh lr, [ip, #-2]! add \outs, \outs, lr 4: .if \load_ins add \registers, \registers, \ins .endif add \code_item, \code_item, #COMPACT_CODE_ITEM_INSNS_OFFSET b 6f 5: // Fetch dex register size. ldrh \registers, [\code_item, #CODE_ITEM_REGISTERS_SIZE_OFFSET] // Fetch outs size. ldrh \outs, [\code_item, #CODE_ITEM_OUTS_SIZE_OFFSET] .if \load_ins ldrh \ins, [\code_item, #CODE_ITEM_INS_SIZE_OFFSET] .endif add \code_item, \code_item, #CODE_ITEM_INSNS_OFFSET 6: .endm // Setup the stack to start executing the method. Expects: // - r0 to contain the ArtMethod // - \code_item to already contain the code item // - rINST, ip, lr to be available // // Outputs // - rINST contains the dex registers size // - ip contains the old stack pointer. // - \code_item is replaced with a pointer to the instructions // - if load_ins is 1, r4 contains the ins // .macro SETUP_STACK_FRAME code_item, refs, fp, cfi_refs, load_ins FETCH_CODE_ITEM_INFO \code_item, rINST, \refs, r4, \load_ins // Compute required frame size: ((2 * rINST) + \refs) * 4 + 12 // 12 is for saving the previous frame, pc, and method being executed. add ip, \refs, rINST, lsl #1 // Compute new stack pointer in lr sub lr, sp, #12 sub lr, lr, ip, lsl #2 // Alignment and lr, lr, #-16 // Set reference and dex registers. add \refs, lr, \refs, lsl #2 add \refs, \refs, #12 add \fp, \refs, rINST, lsl #2 // Now setup the stack pointer. mov ip, sp .cfi_def_cfa_register ip mov sp, lr str ip, [\refs, #-4] CFI_DEF_CFA_BREG_PLUS_UCONST \cfi_refs, -4, CALLEE_SAVES_SIZE // Save the ArtMethod, and use r0 as a temporary. str r0, [sp] // Put nulls in reference frame. cmp rINST, #0 beq 2f mov lr, \refs mov r0, #0 1: str r0, [lr], #4 str r0, [lr], #4 // May clear vreg[0]. cmp lr, \fp blo 1b 2: ldr r0, [sp] // Reload the ArtMethod, expected by the callers. .endm // Increase method hotness and do suspend check before starting executing the method. .macro START_EXECUTING_INSTRUCTIONS ldr r0, [sp] ldrh r2, [r0, #ART_METHOD_HOTNESS_COUNT_OFFSET] add r2, r2, #1 ubfx r2, r2, #0, #NTERP_HOTNESS_BITS strh r2, [r0, #ART_METHOD_HOTNESS_COUNT_OFFSET] // If the counter overflows, handle this in the runtime. cmp r2, #0 beq 2f ldr r0, [rSELF, #THREAD_FLAGS_OFFSET] tst r0, #THREAD_SUSPEND_OR_CHECKPOINT_REQUEST bne 3f 1: FETCH_INST GET_INST_OPCODE ip GOTO_OPCODE ip 2: mov r1, #0 mov r2, rFP bl nterp_hot_method b 1b 3: EXPORT_PC bl art_quick_test_suspend b 1b .endm .macro SPILL_ALL_CALLEE_SAVES SPILL_ALL_CALLEE_SAVE_GPRS @ 9 words (36 bytes) of callee saves. vpush {s16-s31} @ 16 words (64 bytes) of floats. .cfi_adjust_cfa_offset 64 .endm .macro RESTORE_ALL_CALLEE_SAVES lr_to_pc=0 vpop {s16-s31} .cfi_adjust_cfa_offset -64 pop {r4-r7} .cfi_adjust_cfa_offset -16 .cfi_restore r4 .cfi_restore r5 .cfi_restore r6 .cfi_restore r7 // Don't restore r8, the marking register gets updated when coming back from runtime. add sp, sp, #4 .cfi_adjust_cfa_offset -4 .if \lr_to_pc pop {r9-r11, pc} @ 9 words of callee saves and args. .else pop {r9-r11, lr} @ 9 words of callee saves and args. .cfi_adjust_cfa_offset -16 .cfi_restore r9 .cfi_restore r10 .cfi_restore r11 .cfi_restore lr .endif .endm // Helper to setup the stack after doing a nterp to nterp call. This will setup: // - rNEW_FP: the new pointer to dex registers // - rNEW_REFS: the new pointer to references // - rPC: the new PC pointer to execute // - r2: value in instruction to decode the number of arguments. // - r3: first dex register for range invokes, up to 4 arguments for non-range invokes. // - r4: top of dex register array // // The method expects: // - r0 to contain the ArtMethod // - r4 to contain the code item .macro SETUP_STACK_FOR_INVOKE // We do the same stack overflow check as the compiler. See CanMethodUseNterp // in how we limit the maximum nterp frame size. sub ip, sp, #STACK_OVERFLOW_RESERVED_BYTES ldr ip, [ip] // Spill all callee saves to have a consistent stack frame whether we // are called by compiled code or nterp. SPILL_ALL_CALLEE_SAVES // Setup the frame. SETUP_STACK_FRAME r4, rNEW_REFS, rNEW_FP, CFI_NEW_REFS, load_ins=0 // Fetch instruction information before replacing rPC. FETCH_B r2, 0, 1 FETCH r3, 2 // Set the dex pc pointer. mov rPC, r4 // Make r4 point to the top of the dex register array. add r4, rNEW_FP, rINST, lsl #2 CFI_DEFINE_DEX_PC_WITH_OFFSET(CFI_TMP, CFI_DEX, 0) .endm // Setup arguments based on a non-range nterp to nterp call, and start executing // the method. We expect: // - rNEW_FP: the new pointer to dex registers // - rPC: the new PC pointer to execute // - r2: number of arguments (bits 4-7), 5th argument if any (bits 0-3) // - r3: up to four dex register arguments // - r4: top of dex register array // - r1: receiver if non-static. // // Uses r0 and rINST as temporaries. .macro SETUP_NON_RANGE_ARGUMENTS_AND_EXECUTE is_static=0, is_string_init=0 // /* op vA, vB, {vC...vG} */ .if \is_static asrs r0, r2, #4 beq 6f .else asr r0, r2, #4 .endif mov rINST, #-4 cmp r0, #2 blt 1f beq 2f cmp r0, #4 blt 3f beq 4f // We use a decrementing rINST to store references relative // to rNEW_FP and dex registers relative to r4 // // TODO: We could set up rINST as the number of registers (this can be an additional output from // SETUP_STACK_FOR_INVOKE) and then just decrement it by one before copying each arg. // Maybe even introduce macros NEW_VREG_ADDRESS/NEW_VREG_REF_ADDRESS. 5: and r2, r2, #15 GET_VREG_OBJECT r0, r2 str r0, [rNEW_FP, rINST] GET_VREG r0, r2 str r0, [r4, rINST] sub rINST, rINST, #4 4: asr r2, r3, #12 GET_VREG_OBJECT r0, r2 str r0, [rNEW_FP, rINST] GET_VREG r0, r2 str r0, [r4, rINST] sub rINST, rINST, #4 3: ubfx r2, r3, #8, #4 GET_VREG_OBJECT r0, r2 str r0, [rNEW_FP, rINST] GET_VREG r0, r2 str r0, [r4, rINST] sub rINST, rINST, #4 2: ubfx r2, r3, #4, #4 GET_VREG_OBJECT r0, r2 str r0, [rNEW_FP, rINST] GET_VREG r0, r2 str r0, [r4, rINST] .if !\is_string_init sub rINST, rINST, #4 .endif 1: .if \is_string_init // Ignore the first argument .elseif \is_static and r2, r3, #0xf GET_VREG_OBJECT r0, r2 str r0, [rNEW_FP, rINST] GET_VREG r0, r2 str r0, [r4, rINST] .else str r1, [rNEW_FP, rINST] str r1, [r4, rINST] .endif 6: // Start executing the method. mov rFP, rNEW_FP mov rREFS, rNEW_REFS CFI_DEF_CFA_BREG_PLUS_UCONST CFI_REFS, -4, CALLEE_SAVES_SIZE // r8 was used for setting up the frame, restore it now. REFRESH_MARKING_REGISTER // Branch to the main handler, which will reload rIBASE, // that was used for setting up the frame. b .Lexecute_instructions .endm // Setup arguments based on a range nterp to nterp call, and start executing // the method. // - rNEW_FP: the new pointer to dex registers // - rNEW_REFS: the new pointer to references // - rPC: the new PC pointer to execute // - r2: number of arguments // - r3: first dex register // - r4: top of dex register array // - r1: receiver if non-static. // // Expects r0 to be available. .macro SETUP_RANGE_ARGUMENTS_AND_EXECUTE is_static=0, is_string_init=0 mov r0, #-4 .if \is_string_init // Ignore the first argument sub r2, r2, #1 add r3, r3, #1 .elseif !\is_static sub r2, r2, #1 add r3, r3, #1 .endif cmp r2, #0 beq 2f add rREFS, rREFS, r3, lsl #2 // pointer to first argument in reference array add rREFS, rREFS, r2, lsl #2 // pointer to last argument in reference array add rFP, rFP, r3, lsl #2 // pointer to first argument in register array add rFP, rFP, r2, lsl #2 // pointer to last argument in register array 1: ldr r3, [rREFS, #-4]! str r3, [rNEW_FP, r0] subs r2, r2, 1 ldr r3, [rFP, #-4]! str r3, [r4, r0] sub r0, r0, 4 bne 1b 2: .if \is_string_init // Ignore first argument .elseif !\is_static str r1, [rNEW_FP, r0] str r1, [r4, r0] .endif mov rFP, rNEW_FP mov rREFS, rNEW_REFS CFI_DEF_CFA_BREG_PLUS_UCONST CFI_REFS, -4, CALLEE_SAVES_SIZE // r8 was used for setting up the frame, restore it now. REFRESH_MARKING_REGISTER // Branch to the main handler, which will reload rIBASE, // that was used for setting up the frame. b .Lexecute_instructions .endm .macro GET_SHORTY dest, is_interface, is_polymorphic, is_custom push {r0-r3} .if \is_polymorphic ldr r0, [sp, #16] mov r1, rPC bl NterpGetShortyFromInvokePolymorphic .elseif \is_custom ldr r0, [sp, #16] mov r1, rPC bl NterpGetShortyFromInvokeCustom .elseif \is_interface ldr r0, [sp, #16] FETCH r1, 1 bl NterpGetShortyFromMethodId .else bl NterpGetShorty .endif mov \dest, r0 pop {r0-r3} .endm // Input: r0 contains the ArtMethod // Output: r4 contains the code item .macro GET_CODE_ITEM ldr r4, [r0, #ART_METHOD_DATA_OFFSET_32] .endm .macro DO_ENTRY_POINT_CHECK call_compiled_code, name // On entry, the method is r0, the instance is r1 ldr r2, .Lfetch_nterp_\name .Lfetch_location_\name: // Note that this won't work for thumb. sub r2, pc, r2 ldr r3, [r0, #ART_METHOD_QUICK_CODE_OFFSET_32] cmp r2, r3 bne \call_compiled_code .endm // Expects ip and lr to be available. .macro UPDATE_REGISTERS_FOR_STRING_INIT old_value, new_value mov ip, #0 1: GET_VREG_OBJECT lr, ip cmp lr, \old_value bne 2f SET_VREG_OBJECT \new_value, ip 2: add ip, ip, #1 add lr, rREFS, ip, lsl #2 cmp lr, rFP bne 1b .endm // Puts the next floating point argument into the expected register, // fetching values based on a non-range invoke. // Uses ip and lr. .macro LOOP_OVER_SHORTY_LOADING_FPS dreg, sreg, inst, shorty, arg_index, finished, if_double 1: // LOOP ldrb ip, [\shorty], #1 // Load next character in shorty, and increment. cmp ip, #0 beq \finished // if (ip == '\0') goto finished cmp ip, #68 // if (ip == 'D') goto FOUND_DOUBLE beq 2f cmp ip, #70 // if (ip == 'F') goto FOUND_FLOAT beq 3f lsr \inst, \inst, #4 add \arg_index, \arg_index, #1 // Handle extra argument in arg array taken by a long. cmp ip, #74 // if (ip != 'J') goto LOOP bne 1b lsr \inst, \inst, #4 add \arg_index, \arg_index, #1 b 1b // goto LOOP 2: // FOUND_DOUBLE and ip, \inst, #0xf GET_VREG ip, ip lsr \inst, \inst, #4 add \arg_index, \arg_index, #1 cmp \arg_index, #4 beq 5f and lr, \inst, #0xf lsr \inst, \inst, #4 add \arg_index, \arg_index, #1 b 6f 5: FETCH_B lr, 0, 1 and lr, lr, #0xf 6: GET_VREG lr, lr vmov \dreg, ip, lr b \if_double 3: // FOUND_FLOAT cmp \arg_index, #4 beq 7f and ip, \inst, #0xf lsr \inst, \inst, #4 add \arg_index, \arg_index, #1 b 8f 7: FETCH_B ip, 0, 1 and ip, ip, #0xf 8: GET_VREG_FLOAT \sreg, ip .endm // Puts the next int/long/object argument in the expected register, // fetching values based on a non-range invoke. // Uses ip. .macro LOOP_OVER_SHORTY_LOADING_GPRS gpr_reg, inst, shorty, arg_index, finished, if_long, is_r3 1: // LOOP ldrb ip, [\shorty], #1 // Load next character in shorty, and increment. cmp ip, #0 beq \finished // if (ip == '\0') goto finished cmp ip, #74 // if (ip == 'J') goto FOUND_LONG beq 2f cmp ip, #70 // if (ip == 'F') goto SKIP_FLOAT beq 3f cmp ip, #68 // if (ip == 'D') goto SKIP_DOUBLE beq 4f cmp \arg_index, #4 beq 7f and ip, \inst, #0xf lsr \inst, \inst, #4 add \arg_index, \arg_index, #1 b 8f 7: FETCH_B ip, 0, 1 and ip, ip, #0xf 8: GET_VREG \gpr_reg, ip b 5f 2: // FOUND_LONG .if \is_r3 // Put back shorty and exit sub \shorty, \shorty, #1 b 5f .endif and ip, \inst, #0xf GET_VREG ip, ip // The only one possible for non-range long is r2-r3 mov r2, ip lsr \inst, \inst, #4 add \arg_index, \arg_index, #1 cmp \arg_index, #4 beq 9f and ip, \inst, #0xf lsr \inst, \inst, #4 b 10f 9: FETCH_B ip, 0, 1 and ip, ip, #0xf 10: GET_VREG ip, ip // The only one possible for non-range long is r2-r3 mov r3, ip add \arg_index, \arg_index, #1 b \if_long 3: // SKIP_FLOAT lsr \inst, \inst, #4 add \arg_index, \arg_index, #1 b 1b 4: // SKIP_DOUBLE lsr \inst, \inst, #8 add \arg_index, \arg_index, #2 b 1b 5: .endm // Puts the next int/long/object argument in the expected stack slot, // fetching values based on a non-range invoke. // Uses ip as temporary. .macro LOOP_OVER_SHORTY_LOADING_INTs shorty, inst, arg_index, finished, is_string_init 1: // LOOP ldrb ip, [\shorty], #1 // Load next character in shorty, and increment. cmp ip, #0 beq \finished // if (ip == '\0') goto finished cmp ip, #74 // if (ip == 'J') goto FOUND_LONG beq 2f cmp ip, #70 // if (ip == 'F') goto SKIP_FLOAT beq 3f cmp ip, #68 // if (ip == 'D') goto SKIP_DOUBLE beq 4f .if \is_string_init cmp \arg_index, #4 .else cmp \arg_index, #(4+1) // +1 for ArtMethod .endif beq 7f and ip, \inst, #0xf lsr \inst, \inst, #4 b 8f 7: FETCH_B ip, 0, 1 and ip, ip, #0xf 8: GET_VREG ip, ip str ip, [sp, \arg_index, lsl #2] add \arg_index, \arg_index, #1 b 1b 2: // FOUND_LONG and ip, \inst, #0xf GET_VREG ip, ip str ip, [sp, \arg_index, lsl #2] lsr \inst, \inst, #4 add \arg_index, \arg_index, #1 .if \is_string_init cmp \arg_index, #4 .else cmp \arg_index, #(4+1) // +1 for ArtMethod .endif beq 9f and ip, \inst, #0xf lsr \inst, \inst, #4 b 10f 9: FETCH_B ip, 0, 1 and ip, ip, #0xf 10: GET_VREG ip, ip str ip, [sp, \arg_index, lsl #2] add \arg_index, \arg_index, #1 b 1b 3: // SKIP_FLOAT lsr \inst, \inst, #4 add \arg_index, \arg_index, #1 b 1b 4: // SKIP_DOUBLE lsr \inst, \inst, #8 add \arg_index, \arg_index, #2 b 1b .endm .macro SETUP_RETURN_VALUE shorty ldrb ip, [\shorty] cmp ip, #68 // Test if result type char == 'D'. beq 1f cmp ip, #70 // Test if result type char == 'F'. bne 2f vmov r0, s0 b 2f 1: vmov r0, r1, d0 2: .endm .macro GET_SHORTY_SLOW_PATH dest, is_interface // Save all registers that can hold arguments in the fast path. vpush {s0} push {r0-r2} .if \is_interface ldr r0, [sp, #16] FETCH r1, 1 bl NterpGetShortyFromMethodId .else bl NterpGetShorty .endif mov \dest, r0 pop {r0-r2} vpop {s0} .endm .macro COMMON_INVOKE_NON_RANGE is_static=0, is_interface=0, suffix="", is_string_init=0, is_polymorphic=0, is_custom=0 .if \is_polymorphic // We always go to compiled code for polymorphic calls. .elseif \is_custom // We always go to compiled code for custom calls. .else DO_ENTRY_POINT_CHECK .Lcall_compiled_code_\suffix, \suffix GET_CODE_ITEM .if \is_string_init bl nterp_to_nterp_string_init_non_range .elseif \is_static bl nterp_to_nterp_static_non_range .else bl nterp_to_nterp_instance_non_range .endif b .Ldone_return_\suffix .Lfetch_nterp_\suffix: .word (.Lfetch_location_\suffix+8) - ExecuteNterpImpl .endif .Lcall_compiled_code_\suffix: .if \is_polymorphic // No fast path for polymorphic calls. .elseif \is_custom // No fast path for custom calls. .elseif \is_string_init // No fast path for string.init. .else ldr ip, [r0, #ART_METHOD_ACCESS_FLAGS_OFFSET] tst ip, #ART_METHOD_NTERP_INVOKE_FAST_PATH_FLAG beq .Lfast_path_with_few_args_\suffix FETCH_B rINST, 0, 1 .if \is_static asrs lr, rINST, #4 beq .Linvoke_fast_path_\suffix .else asr lr, rINST, #4 cmp lr, #1 beq .Linvoke_fast_path_\suffix .endif FETCH ip, 2 cmp lr, #2 .if \is_static blt .Lone_arg_fast_path_\suffix .endif beq .Ltwo_args_fast_path_\suffix cmp lr, #4 blt .Lthree_args_fast_path_\suffix beq .Lfour_args_fast_path_\suffix and rINST, rINST, #15 GET_VREG rINST, rINST str rINST, [sp, #(4 + 4 * 4)] .Lfour_args_fast_path_\suffix: asr rINST, ip, #12 GET_VREG rINST, rINST str rINST, [sp, #(4 + 3 * 4)] .Lthree_args_fast_path_\suffix: ubfx rINST, ip, #8, #4 GET_VREG r3, rINST .Ltwo_args_fast_path_\suffix: ubfx rINST, ip, #4, #4 GET_VREG r2, rINST .Lone_arg_fast_path_\suffix: .if \is_static and rINST, ip, #0xf GET_VREG r1, rINST .else // First argument already in r1. .endif .Linvoke_fast_path_\suffix: .if \is_interface // Setup hidden argument. mov ip, r4 .endif ldr lr, [r0, #ART_METHOD_QUICK_CODE_OFFSET_32] blx lr FETCH_ADVANCE_INST 3 GET_INST_OPCODE ip GOTO_OPCODE ip .Lfast_path_with_few_args_\suffix: // Fast path when we have zero or one argument (modulo 'this'). If there // is one argument, we can put it in both floating point and core register. FETCH_B r2, 0, 1 asr r2, r2, #4 // number of arguments .if \is_static cmp r2, #1 blt .Linvoke_with_few_args_\suffix bne .Lget_shorty_\suffix FETCH r2, 2 and r2, r2, #0xf // dex register of first argument GET_VREG r1, r2 vmov s0, r1 .else cmp r2, #2 blt .Linvoke_with_few_args_\suffix bne .Lget_shorty_\suffix FETCH r2, 2 ubfx r2, r2, #4, #4 // dex register of second argument GET_VREG r2, r2 vmov s0, r2 .endif .Linvoke_with_few_args_\suffix: // Check if the next instruction is move-result or move-result-wide. // If it is, we fetch the shorty and jump to the regular invocation. FETCH r3, 3 and r3, r3, #0xfe cmp r3, #0x0a beq .Lget_shorty_and_invoke_\suffix .if \is_interface // Setup hidden argument. mov ip, r4 .endif ldr lr, [r0, #ART_METHOD_QUICK_CODE_OFFSET_32] blx lr FETCH_ADVANCE_INST 3 GET_INST_OPCODE ip GOTO_OPCODE ip .Lget_shorty_and_invoke_\suffix: .if \is_interface // Save hidden argument. vmov s16, r4 .endif GET_SHORTY_SLOW_PATH rINST, \is_interface b .Lgpr_setup_finished_\suffix .endif .Lget_shorty_\suffix: .if \is_interface // Save hidden argument. vmov s16, r4 .endif GET_SHORTY rINST, \is_interface, \is_polymorphic, \is_custom // From this point: // - rINST contains shorty (in callee-save to switch over return value after call). // - r0 contains method // - r1 contains 'this' pointer for instance method. // We need three registers. add r3, rINST, #1 // shorty + 1 ; ie skip return arg character FETCH r2, 2 // arguments .if \is_string_init lsr r2, r2, #4 mov r4, #1 // ignore first argument .elseif \is_static mov r4, #0 // arg_index .else lsr r2, r2, #4 mov r4, #1 // ignore first argument .endif LOOP_OVER_SHORTY_LOADING_FPS d0, s0, r2, r3, r4, .Lxmm_setup_finished_\suffix, .Ld1_s2_\suffix .Ld1_s1_\suffix: LOOP_OVER_SHORTY_LOADING_FPS d1, s1, r2, r3, r4, .Lxmm_setup_finished_\suffix, .Ld2_s1_\suffix .Ld1_s2_\suffix: LOOP_OVER_SHORTY_LOADING_FPS d1, s2, r2, r3, r4, .Lxmm_setup_finished_\suffix, .Ls4_\suffix .Ld2_s3_\suffix: LOOP_OVER_SHORTY_LOADING_FPS d2, s3, r2, r3, r4, .Lxmm_setup_finished_\suffix, .Lxmm_setup_finished_\suffix b .Ls4_\suffix .Ld2_s1_\suffix: LOOP_OVER_SHORTY_LOADING_FPS d2, s1, r2, r3, r4, .Lxmm_setup_finished_\suffix, .Lxmm_setup_finished_\suffix .Ls4_\suffix: // If we arrive here, we can only have a float. LOOP_OVER_SHORTY_LOADING_FPS d2, s4, r2, r3, r4, .Lxmm_setup_finished_\suffix, .Lxmm_setup_finished_\suffix .Lxmm_setup_finished_\suffix: add r4, rINST, #1 // shorty + 1 ; ie skip return arg character FETCH r8, 2 // arguments .if \is_string_init lsr r8, r8, #4 mov lr, #1 // ignore first argument LOOP_OVER_SHORTY_LOADING_GPRS r1, r8, r4, lr, .Lgpr_setup_finished_\suffix, .Lif_long_\suffix, is_r3=0 .elseif \is_static mov lr, #0 // arg_index LOOP_OVER_SHORTY_LOADING_GPRS r1, r8, r4, lr, .Lgpr_setup_finished_\suffix, .Lif_long_\suffix, is_r3=0 .else lsr r8, r8, #4 mov lr, #1 // ignore first argument .endif LOOP_OVER_SHORTY_LOADING_GPRS r2, r8, r4, lr, .Lgpr_setup_finished_\suffix, .Lif_long_\suffix, is_r3=0 LOOP_OVER_SHORTY_LOADING_GPRS r3, r8, r4, lr, .Lgpr_setup_finished_\suffix, .Lif_long_\suffix, is_r3=1 .Lif_long_\suffix: // Store in the outs array (stored above the ArtMethod in the stack). We only do this for non-string-init // calls as the index is already adjusted above. .if !\is_string_init add lr, lr, #1 .endif LOOP_OVER_SHORTY_LOADING_INTs r4, r8, lr, .Lgpr_setup_finished_\suffix, \is_string_init .Lgpr_setup_finished_\suffix: REFRESH_MARKING_REGISTER // r8 was used when setting parameters, restore it. .if \is_polymorphic bl art_quick_invoke_polymorphic .elseif \is_custom bl art_quick_invoke_custom .else .if \is_interface // Setup hidden argument. vmov ip, s16 .endif ldr lr, [r0, #ART_METHOD_QUICK_CODE_OFFSET_32] blx lr .endif SETUP_RETURN_VALUE rINST .Ldone_return_\suffix: /* resume execution of caller */ .if \is_string_init FETCH ip, 2 // arguments and ip, ip, #0xf GET_VREG r1, ip UPDATE_REGISTERS_FOR_STRING_INIT r1, r0 .endif .if \is_polymorphic FETCH_ADVANCE_INST 4 .else FETCH_ADVANCE_INST 3 .endif GET_INST_OPCODE ip GOTO_OPCODE ip .endm // Puts the next int/long/object argument in the expected register, // fetching values based on a range invoke. // Uses ip as temporary. .macro LOOP_RANGE_OVER_SHORTY_LOADING_GPRS reg32, shorty, arg_index, stack_index, finished, if_long, is_r3 1: // LOOP ldrb ip, [\shorty], #1 // Load next character in shorty, and increment. cmp ip, #0 beq \finished // if (ip == '\0') goto finished cmp ip, #74 // if (ip == 'J') goto FOUND_LONG beq 2f cmp ip, #70 // if (ip == 'F') goto SKIP_FLOAT beq 3f cmp ip, #68 // if (ip == 'D') goto SKIP_DOUBLE beq 4f GET_VREG \reg32, \arg_index add \arg_index, \arg_index, #1 add \stack_index, \stack_index, #1 b 5f 2: // FOUND_LONG .if \is_r3 // Put back shorty and jump to \if_long sub \shorty, \shorty, #1 .else GET_VREG r2, \arg_index add \arg_index, \arg_index, #1 add \stack_index, \stack_index, #1 GET_VREG r3, \arg_index add \arg_index, \arg_index, #1 add \stack_index, \stack_index, #1 .endif b \if_long 3: // SKIP_FLOAT add \arg_index, \arg_index, #1 add \stack_index, \stack_index, #1 b 1b 4: // SKIP_DOUBLE add \arg_index, \arg_index, #2 add \stack_index, \stack_index, #2 b 1b 5: .endm // Puts the next int/long/object argument in the expected stack slot, // fetching values based on a range invoke. // Uses ip as temporary. .macro LOOP_RANGE_OVER_INTs shorty, arg_index, stack_index, finished 1: // LOOP ldrb ip, [\shorty], #1 // Load next character in shorty, and increment. cmp ip, #0 beq \finished // if (ip == '\0') goto finished cmp ip, #74 // if (ip == 'J') goto FOUND_LONG beq 2f cmp ip, #70 // if (ip == 'F') goto SKIP_FLOAT beq 3f cmp ip, #68 // if (ip == 'D') goto SKIP_DOUBLE beq 4f GET_VREG ip, \arg_index str ip, [sp, \stack_index, lsl #2] add \arg_index, \arg_index, #1 add \stack_index, \stack_index, #1 b 1b 2: // FOUND_LONG GET_VREG ip, \arg_index str ip, [sp, \stack_index, lsl #2] add \arg_index, \arg_index, #1 add \stack_index, \stack_index, #1 GET_VREG ip, \arg_index str ip, [sp, \stack_index, lsl #2] add \arg_index, \arg_index, #1 add \stack_index, \stack_index, #1 b 1b 3: // SKIP_FLOAT add \arg_index, \arg_index, #1 add \stack_index, \stack_index, #1 b 1b 4: // SKIP_DOUBLE add \arg_index, \arg_index, #2 add \stack_index, \stack_index, #2 b 1b .endm .macro COMMON_INVOKE_RANGE is_static=0, is_interface=0, suffix="", is_string_init=0, is_polymorphic=0, is_custom=0 .if \is_polymorphic // We always go to compiled code for polymorphic calls. .elseif \is_custom // We always go to compiled code for custom calls. .else DO_ENTRY_POINT_CHECK .Lcall_compiled_code_range_\suffix, range_\suffix GET_CODE_ITEM .if \is_string_init bl nterp_to_nterp_string_init_range .elseif \is_static bl nterp_to_nterp_static_range .else bl nterp_to_nterp_instance_range .endif b .Ldone_return_range_\suffix .Lfetch_nterp_range_\suffix: .word (.Lfetch_location_range_\suffix+8) - ExecuteNterpImpl .endif .Lcall_compiled_code_range_\suffix: .if \is_polymorphic // No fast path for polymorphic calls. .elseif \is_custom // No fast path for custom calls. .elseif \is_string_init // No fast path for string.init. .else ldr ip, [r0, #ART_METHOD_ACCESS_FLAGS_OFFSET] tst ip, #ART_METHOD_NTERP_INVOKE_FAST_PATH_FLAG beq .Lfast_path_with_few_args_range_\suffix FETCH_B ip, 0, 1 // Number of arguments .if \is_static cmp ip, #0 .else cmp ip, #1 .endif beq .Linvoke_fast_path_range_\suffix FETCH lr, 2 // dex register of first argument add lr, rFP, lr, lsl #2 // location of first dex register value. .if \is_static cmp ip, #2 blt .Lone_arg_fast_path_range_\suffix beq .Ltwo_args_fast_path_range_\suffix cmp ip, #3 .else cmp ip, #3 blt .Ltwo_args_fast_path_range_\suffix .endif beq .Lthree_args_fast_path_range_\suffix add rINST, sp, #4 // Add space for the ArtMethod .Lloop_over_fast_path_range_\suffix: sub ip, ip, #1 ldr r3, [lr, ip, lsl #2] str r3, [rINST, ip, lsl #2] cmp ip, #3 bne .Lloop_over_fast_path_range_\suffix .Lthree_args_fast_path_range_\suffix: ldr r3, [lr, #8] .Ltwo_args_fast_path_range_\suffix: ldr r2, [lr, #4] .Lone_arg_fast_path_range_\suffix: .if \is_static ldr r1, [lr, #0] .else // First argument already in r1. .endif .Linvoke_fast_path_range_\suffix: .if \is_interface // Setup hidden argument. mov ip, r4 .endif ldr lr, [r0, #ART_METHOD_QUICK_CODE_OFFSET_32] blx lr FETCH_ADVANCE_INST 3 GET_INST_OPCODE ip GOTO_OPCODE ip .Lfast_path_with_few_args_range_\suffix: // Fast path when we have zero or one argument (modulo 'this'). If there // is one argument, we can put it in both floating point and core register. FETCH_B r2, 0, 1 // number of arguments .if \is_static cmp r2, #1 blt .Linvoke_with_few_args_range_\suffix bne .Lget_shorty_range_\suffix FETCH r3, 2 // dex register of first argument GET_VREG r1, r3 vmov s0, r1 .else cmp r2, #2 blt .Linvoke_with_few_args_range_\suffix bne .Lget_shorty_range_\suffix FETCH r3, 2 // dex register of first argument add r3, r3, #1 // Add 1 for next argument GET_VREG r2, r3 vmov s0, r2 .endif .Linvoke_with_few_args_range_\suffix: // Check if the next instruction is move-result or move-result-wide. // If it is, we fetch the shorty and jump to the regular invocation. FETCH r3, 3 and r3, r3, #0xfe cmp r3, #0x0a beq .Lget_shorty_and_invoke_range_\suffix .if \is_interface // Setup hidden argument. mov ip, r4 .endif ldr lr, [r0, #ART_METHOD_QUICK_CODE_OFFSET_32] blx lr FETCH_ADVANCE_INST 3 GET_INST_OPCODE ip GOTO_OPCODE ip .Lget_shorty_and_invoke_range_\suffix: .if \is_interface // Save hidden argument. vmov s16, r4 .endif GET_SHORTY_SLOW_PATH rINST, \is_interface b .Lgpr_setup_finished_range_\suffix .endif .Lget_shorty_range_\suffix: .if \is_interface // Save hidden argument. vmov s16, r4 .endif GET_SHORTY rINST, \is_interface, \is_polymorphic, \is_custom // From this point: // - rINST contains shorty (in callee-save to switch over return value after call). // - r0 contains method // - r1 contains 'this' pointer for instance method. // // Save r0 and r1 before calling NterpSetupArm32Fprs. push {r0, r1} add r0, rINST, #1 // shorty + 1 ; ie skip return arg character FETCH r1, 2 // arguments .if \is_string_init add r1, r1, #1 // arg start index mov r2, #1 // index in stack .elseif \is_static mov r2, #0 // index in stack .else add r1, r1, #1 // arg start index mov r2, #1 // index in stack .endif vpush {s0-s15} mov r3, sp // Pass the stack address for arguments, +16 for fprs, +2 for saved registers, // +1 for ArtMethod. add lr, sp, #((16 + 2 + 1) * 4) push {rFP, lr} bl NterpSetupArm32Fprs add sp, sp, #8 vpop {s0-s15} pop {r0, r1} .Lxmm_setup_finished_range_\suffix: add r8, rINST, #1 // shorty + 1 ; ie skip return arg character FETCH lr, 2 // arguments .if \is_string_init add lr, lr, #1 // arg start index mov r4, #0 // index in stack LOOP_RANGE_OVER_SHORTY_LOADING_GPRS r1, r8, lr, r4, .Lgpr_setup_finished_range_\suffix, .Lif_long_range_\suffix, is_r3=0 .elseif \is_static mov r4, #0 // index in stack LOOP_RANGE_OVER_SHORTY_LOADING_GPRS r1, r8, lr, r4, .Lgpr_setup_finished_range_\suffix, .Lif_long_range_\suffix, is_r3=0 .else add lr, lr, #1 // arg start index mov r4, #1 // index in stack .endif LOOP_RANGE_OVER_SHORTY_LOADING_GPRS r2, r8, lr, r4, .Lgpr_setup_finished_range_\suffix, .Lif_long_range_\suffix, is_r3=0 LOOP_RANGE_OVER_SHORTY_LOADING_GPRS r3, r8, lr, r4, .Lgpr_setup_finished_range_\suffix, .Lif_long_range_\suffix, is_r3=1 .Lif_long_range_\suffix: // Add 1 word for the ArtMethod stored before the outs. add r4, r4, #1 LOOP_RANGE_OVER_INTs r8, lr, r4, .Lgpr_setup_finished_range_\suffix .Lgpr_setup_finished_range_\suffix: REFRESH_MARKING_REGISTER // r8 was used when setting parameters, restore it. .if \is_polymorphic bl art_quick_invoke_polymorphic .elseif \is_custom bl art_quick_invoke_custom .else .if \is_interface // Setup hidden argument. vmov ip, s16 .endif ldr lr, [r0, #ART_METHOD_QUICK_CODE_OFFSET_32] blx lr .endif SETUP_RETURN_VALUE rINST .Ldone_return_range_\suffix: /* resume execution of caller */ .if \is_string_init FETCH ip, 2 // arguments GET_VREG r1, ip UPDATE_REGISTERS_FOR_STRING_INIT r1, r0 .endif .if \is_polymorphic FETCH_ADVANCE_INST 4 .else FETCH_ADVANCE_INST 3 .endif GET_INST_OPCODE ip GOTO_OPCODE ip .endm .macro WRITE_BARRIER_IF_OBJECT is_object, value, holder, label, tmp .if \is_object // In T32, we would use `SMART_CBZ \value, \label` cmp \value, #0 beq \label ldr ip, [rSELF, #THREAD_CARD_TABLE_OFFSET] lsr \tmp, \holder, #CARD_TABLE_CARD_SHIFT strb ip, [ip, \tmp] \label: .endif .endm .macro LDREXD_STREXD_LOOP addr, load1, load2, store1, store2, tmp, label \label: ldrexd \load1, \load2, [\addr] strexd \tmp, \store1, \store2, [\addr] cmp \tmp, #0 bne \label .endm .macro ATOMIC_LOAD64 addr, load1, load2, tmp, label LDREXD_STREXD_LOOP \addr, \load1, \load2, \load1, \load2, \tmp, \label .endm .macro ATOMIC_STORE64 addr, store1, store2, tmp1, tmp2, label LDREXD_STREXD_LOOP \addr, \tmp1, \tmp2, \store1, \store2, \tmp1, \label .endm // Fetch some information from the thread cache. // Uses ip and lr as temporaries. .macro FETCH_FROM_THREAD_CACHE dest_reg, slow_path add ip, rSELF, #THREAD_INTERPRETER_CACHE_OFFSET // cache address ubfx lr, rPC, #2, #THREAD_INTERPRETER_CACHE_SIZE_LOG2 // entry index add ip, ip, lr, lsl #3 // entry address within the cache // In T32, we would use `ldrd ip, \dest_reg, [ip]` ldr \dest_reg, [ip, #4] // value (offset) ldr ip, [ip] // entry key (pc) cmp ip, rPC bne \slow_path .endm // Puts the next int/long/object parameter passed in physical register // in the expected dex register array entry, and in case of object in the // expected reference array entry. // Uses ip as temporary. .macro LOOP_OVER_SHORTY_STORING_GPRS gpr_32, shorty, arg_offset, regs, refs, finished, if_long, is_r3 1: // LOOP ldrb ip, [\shorty], #1 // Load next character in shorty, and increment. cmp ip, #0 beq \finished // if (ip == '\0') goto finished cmp ip, #74 // if (ip == 'J') goto FOUND_LONG beq 2f cmp ip, #70 // if (ip == 'F') goto SKIP_FLOAT beq 3f cmp ip, #68 // if (ip == 'D') goto SKIP_DOUBLE beq 4f str \gpr_32, [\regs, \arg_offset] cmp ip, #76 // if (ip != 'L') goto NOT_REFERENCE bne 6f str \gpr_32, [\refs, \arg_offset] 6: // NOT_REFERENCE add \arg_offset, \arg_offset, #4 b 5f 2: // FOUND_LONG .if \is_r3 // Put back shorty and jump to \if_long sub \shorty, \shorty, #1 .else // A long can only be in r2, r3 str r2, [\regs, \arg_offset] add \arg_offset, \arg_offset, #4 str r3, [\regs, \arg_offset] add \arg_offset, \arg_offset, #4 .endif b \if_long 3: // SKIP_FLOAT add \arg_offset, \arg_offset, #4 b 1b 4: // SKIP_DOUBLE add \arg_offset, \arg_offset, #8 b 1b 5: .endm // Puts the next int/long/object parameter passed in stack // in the expected dex register array entry, and in case of object in the // expected reference array entry. .macro LOOP_OVER_INTs shorty, arg_offset, regs, refs, stack_ptr, tmp1, tmp2, finished 1: // LOOP ldrb \tmp1, [\shorty], #1 // Load next character in shorty, and increment. cmp \tmp1, #0 beq \finished // if (\tmp1 == '\0') goto finished cmp \tmp1, #74 // if (\tmp1 == 'J') goto FOUND_LONG beq 2f cmp \tmp1, #70 // if (\tmp1 == 'F') goto SKIP_FLOAT beq 3f cmp \tmp1, #68 // if (\tmp1 == 'D') goto SKIP_DOUBLE beq 4f add \tmp2, \stack_ptr, \arg_offset ldr \tmp2, [\tmp2, #OFFSET_TO_FIRST_ARGUMENT_IN_STACK] str \tmp2, [\regs, \arg_offset] cmp \tmp1, #76 // if (\tmp1 != 'L') goto loop bne 3f str \tmp2, [\refs, \arg_offset] add \arg_offset, \arg_offset, #4 b 1b 2: // FOUND_LONG add \tmp1, \stack_ptr, \arg_offset ldr \tmp1, [\tmp1, #OFFSET_TO_FIRST_ARGUMENT_IN_STACK] str \tmp1, [\regs, \arg_offset] add \arg_offset, \arg_offset, #4 add \tmp1, \stack_ptr, \arg_offset ldr \tmp1, [\tmp1, #OFFSET_TO_FIRST_ARGUMENT_IN_STACK] str \tmp1, [\regs, \arg_offset] add \arg_offset, \arg_offset, #4 b 1b 3: // SKIP_FLOAT add \arg_offset, \arg_offset, #4 b 1b 4: // SKIP_DOUBLE add \arg_offset, \arg_offset, #8 b 1b .endm .macro SETUP_REFERENCE_PARAMETER_IN_GPR gpr32, regs, refs, ins, arg_offset, finished str \gpr32, [\regs, \arg_offset] subs \ins, \ins, #1 str \gpr32, [\refs, \arg_offset] add \arg_offset, \arg_offset, #4 beq \finished .endm .macro SETUP_REFERENCE_PARAMETERS_IN_STACK regs, refs, ins, stack_ptr, arg_offset 1: ldr ip, [\stack_ptr, \arg_offset] subs \ins, \ins, #1 str ip, [\regs, \arg_offset] str ip, [\refs, \arg_offset] add \arg_offset, \arg_offset, #4 bne 1b .endm %def entry(): /* * ArtMethod entry point. * * On entry: * r0 ArtMethod* callee * rest method parameters */ OAT_ENTRY ExecuteNterpImpl, EndExecuteNterpImpl .cfi_startproc sub ip, sp, #STACK_OVERFLOW_RESERVED_BYTES ldr ip, [ip] /* Spill callee save regs */ SPILL_ALL_CALLEE_SAVES ldr rPC, [r0, #ART_METHOD_DATA_OFFSET_32] // Setup the stack for executing the method. SETUP_STACK_FRAME rPC, rREFS, rFP, CFI_REFS, load_ins=1 // Setup the parameters cmp r4, #0 beq .Lxmm_setup_finished sub rINST, rINST, r4 ldr r8, [r0, #ART_METHOD_ACCESS_FLAGS_OFFSET] lsl rINST, rINST, #2 // rINST is now the offset for inputs into the registers array. mov rIBASE, ip // rIBASE contains the old stack pointer tst r8, #ART_METHOD_NTERP_ENTRY_POINT_FAST_PATH_FLAG beq .Lsetup_slow_path // Setup pointer to inputs in FP and pointer to inputs in REFS add lr, rFP, rINST add r8, rREFS, rINST mov r0, #0 SETUP_REFERENCE_PARAMETER_IN_GPR r1, lr, r8, r4, r0, .Lxmm_setup_finished SETUP_REFERENCE_PARAMETER_IN_GPR r2, lr, r8, r4, r0, .Lxmm_setup_finished SETUP_REFERENCE_PARAMETER_IN_GPR r3, lr, r8, r4, r0, .Lxmm_setup_finished add rIBASE, rIBASE, #OFFSET_TO_FIRST_ARGUMENT_IN_STACK SETUP_REFERENCE_PARAMETERS_IN_STACK lr, r8, r4, rIBASE, r0 b .Lxmm_setup_finished .Lsetup_slow_path: // If the method is not static and there is one argument ('this'), we don't need to fetch the // shorty. tst r8, #ART_METHOD_IS_STATIC_FLAG bne .Lsetup_with_shorty str r1, [rFP, rINST] str r1, [rREFS, rINST] cmp r4, #1 beq .Lxmm_setup_finished .Lsetup_with_shorty: // Save arguments that were passed before calling into the runtime. // No need to save r0 (ArtMethod) as we're not using it later in this code. // Save r4 for stack aligment. // TODO: Get shorty in a better way and remove below push {r1-r4} vpush {s0-s15} bl NterpGetShorty vpop {s0-s15} pop {r1-r4} mov ip, r8 add r8, rREFS, rINST add r7, rFP, rINST mov r4, #0 // Setup shorty, pointer to inputs in FP and pointer to inputs in REFS add lr, r0, #1 // shorty + 1 ; ie skip return arg character tst ip, #ART_METHOD_IS_STATIC_FLAG bne .Lhandle_static_method add r7, r7, #4 add r8, r8, #4 add rIBASE, rIBASE, #4 b .Lcontinue_setup_gprs .Lhandle_static_method: LOOP_OVER_SHORTY_STORING_GPRS r1, lr, r4, r7, r8, .Lgpr_setup_finished, .Lif_long, is_r3=0 .Lcontinue_setup_gprs: LOOP_OVER_SHORTY_STORING_GPRS r2, lr, r4, r7, r8, .Lgpr_setup_finished, .Lif_long, is_r3=0 LOOP_OVER_SHORTY_STORING_GPRS r3, lr, r4, r7, r8, .Lgpr_setup_finished, .Lif_long, is_r3=1 .Lif_long: LOOP_OVER_INTs lr, r4, r7, r8, rIBASE, ip, r1, .Lgpr_setup_finished .Lgpr_setup_finished: add r0, r0, #1 // shorty + 1 ; ie skip return arg character mov r1, r7 add r2, rIBASE, #OFFSET_TO_FIRST_ARGUMENT_IN_STACK vpush {s0-s15} mov r3, sp bl NterpStoreArm32Fprs add sp, sp, #(16 * 4) .Lxmm_setup_finished: CFI_DEFINE_DEX_PC_WITH_OFFSET(CFI_TMP, CFI_DEX, 0) // r8 was used for setting up the frame, restore it now. REFRESH_MARKING_REGISTER .Lexecute_instructions: // Set rIBASE adr rIBASE, artNterpAsmInstructionStart /* start executing the instruction at rPC */ START_EXECUTING_INSTRUCTIONS /* NOTE: no fallthrough */ // cfi info continues, and covers the whole nterp implementation. SIZE ExecuteNterpImpl %def opcode_pre(): %def helpers(): %def footer(): /* * =========================================================================== * Common subroutines and data * =========================================================================== */ .text .align 2 // Enclose all code below in a symbol (which gets printed in backtraces). NAME_START nterp_helper // Note: mterp also uses the common_* names below for helpers, but that's OK // as the assembler compiled each interpreter separately. common_errDivideByZero: EXPORT_PC bl art_quick_throw_div_zero // Expect index in r1, length in r3 common_errArrayIndex: EXPORT_PC mov r0, r1 mov r1, r3 bl art_quick_throw_array_bounds common_errNullObject: EXPORT_PC bl art_quick_throw_null_pointer_exception NterpCommonInvokeStatic: COMMON_INVOKE_NON_RANGE is_static=1, suffix="invokeStatic" NterpCommonInvokeStaticRange: COMMON_INVOKE_RANGE is_static=1, suffix="invokeStatic" NterpCommonInvokeInstance: COMMON_INVOKE_NON_RANGE suffix="invokeInstance" NterpCommonInvokeInstanceRange: COMMON_INVOKE_RANGE suffix="invokeInstance" NterpCommonInvokeInterface: COMMON_INVOKE_NON_RANGE is_interface=1, suffix="invokeInterface" NterpCommonInvokeInterfaceRange: COMMON_INVOKE_RANGE is_interface=1, suffix="invokeInterface" NterpCommonInvokePolymorphic: COMMON_INVOKE_NON_RANGE is_polymorphic=1, suffix="invokePolymorphic" NterpCommonInvokePolymorphicRange: COMMON_INVOKE_RANGE is_polymorphic=1, suffix="invokePolymorphic" NterpCommonInvokeCustom: COMMON_INVOKE_NON_RANGE is_static=1, is_custom=1, suffix="invokeCustom" NterpCommonInvokeCustomRange: COMMON_INVOKE_RANGE is_static=1, is_custom=1, suffix="invokeCustom" NterpHandleStringInit: COMMON_INVOKE_NON_RANGE is_string_init=1, suffix="stringInit" NterpHandleStringInitRange: COMMON_INVOKE_RANGE is_string_init=1, suffix="stringInit" NterpNewArray: /* new-array vA, vB, class@CCCC */ EXPORT_PC // Fast-path which gets the class from thread-local cache. FETCH_FROM_THREAD_CACHE r0, 2f cmp rMR, #0 bne 3f 1: lsr r1, rINST, #12 // r1<- B GET_VREG r1, r1 // r1<- vB (array length) ldr lr, [rSELF, #THREAD_ALLOC_ARRAY_ENTRYPOINT_OFFSET] blx lr ubfx r1, rINST, #8, #4 // r1<- A SET_VREG_OBJECT r0, r1 FETCH_ADVANCE_INST 2 GET_INST_OPCODE ip GOTO_OPCODE ip 2: mov r0, rSELF ldr r1, [sp] mov r2, rPC bl nterp_get_class_or_allocate_object b 1b 3: bl art_quick_read_barrier_mark_reg00 b 1b NterpHandleHotnessOverflow: add r1, rPC, rINST, lsl #1 mov r2, rFP bl nterp_hot_method cmp r0, #0 bne 1f add r2, rINST, rINST // w2<- byte offset FETCH_ADVANCE_INST_RB r2 // update rPC, load rINST GET_INST_OPCODE ip // extract opcode from rINST GOTO_OPCODE ip // jump to next instruction 1: // Drop the current frame. ldr ip, [rREFS, #-4] mov sp, ip .cfi_def_cfa sp, CALLEE_SAVES_SIZE // The transition frame of type SaveAllCalleeSaves saves r4, r8, and r9, // but not managed ABI. So we need to restore callee-saves of the nterp frame, // and save managed ABI callee saves, which will be restored by the callee upon // return. RESTORE_ALL_CALLEE_SAVES push {r5-r7, r10-r11, lr} .cfi_adjust_cfa_offset 24 .cfi_rel_offset r5, 0 .cfi_rel_offset r6, 4 .cfi_rel_offset r7, 8 .cfi_rel_offset r10, 12 .cfi_rel_offset r11, 16 .cfi_rel_offset lr, 20 vpush {s16-s31} .cfi_adjust_cfa_offset 64 // Setup the new frame ldr r1, [r0, #OSR_DATA_FRAME_SIZE] // Given stack size contains all callee saved registers, remove them. sub r1, r1, #(CALLEE_SAVES_SIZE - 12) // We know r1 cannot be 0, as it at least contains the ArtMethod. // Remember CFA in a callee-save register. mov rINST, sp .cfi_def_cfa_register rINST sub sp, sp, r1 add r2, r0, #OSR_DATA_MEMORY 2: sub r1, r1, #4 ldr ip, [r2, r1] str ip, [sp, r1] cmp r1, #0 bne 2b // Fetch the native PC to jump to and save it in a callee-save register. ldr rFP, [r0, #OSR_DATA_NATIVE_PC] // Free the memory holding OSR Data. bl free // Jump to the compiled code. bx rFP // This is the logical end of ExecuteNterpImpl, where the frame info applies. // EndExecuteNterpImpl includes the methods below as we want the runtime to // see them as part of the Nterp PCs. .cfi_endproc nterp_to_nterp_static_non_range: .cfi_startproc SETUP_STACK_FOR_INVOKE SETUP_NON_RANGE_ARGUMENTS_AND_EXECUTE is_static=1, is_string_init=0 .cfi_endproc nterp_to_nterp_string_init_non_range: .cfi_startproc SETUP_STACK_FOR_INVOKE SETUP_NON_RANGE_ARGUMENTS_AND_EXECUTE is_static=0, is_string_init=1 .cfi_endproc nterp_to_nterp_instance_non_range: .cfi_startproc SETUP_STACK_FOR_INVOKE SETUP_NON_RANGE_ARGUMENTS_AND_EXECUTE is_static=0, is_string_init=0 .cfi_endproc nterp_to_nterp_static_range: .cfi_startproc SETUP_STACK_FOR_INVOKE SETUP_RANGE_ARGUMENTS_AND_EXECUTE is_static=1, is_string_init=0 .cfi_endproc nterp_to_nterp_string_init_range: .cfi_startproc SETUP_STACK_FOR_INVOKE SETUP_RANGE_ARGUMENTS_AND_EXECUTE is_static=0, is_string_init=1 .cfi_endproc nterp_to_nterp_instance_range: .cfi_startproc SETUP_STACK_FOR_INVOKE SETUP_RANGE_ARGUMENTS_AND_EXECUTE is_static=0, is_string_init=0 .cfi_endproc NAME_END nterp_helper // This is the end of PCs contained by the OatQuickMethodHeader created for the interpreter // entry point. .type EndExecuteNterpImpl, #function .hidden EndExecuteNterpImpl .global EndExecuteNterpImpl EndExecuteNterpImpl: /* * Convert the double in r0/r1 to a long in r0/r1. * * We have to clip values to long min/max per the specification. The * expected common case is a "reasonable" value that converts directly * to modest integer. The EABI convert function isn't doing this for us. */ nterp_d2l_doconv: ubfx r2, r1, #20, #11 @ grab the exponent movw r3, #0x43e cmp r2, r3 @ MINLONG < x > MAXLONG? bhs d2l_special_cases b __aeabi_d2lz @ tail call to convert double to long d2l_special_cases: movw r3, #0x7ff cmp r2, r3 beq d2l_maybeNaN @ NaN? d2l_notNaN: adds r1, r1, r1 @ sign bit to carry mov r0, #0xffffffff @ assume maxlong for lsw mov r1, #0x7fffffff @ assume maxlong for msw adc r0, r0, #0 adc r1, r1, #0 @ convert maxlong to minlong if exp negative bx lr @ return d2l_maybeNaN: orrs r3, r0, r1, lsl #12 beq d2l_notNaN @ if fraction is non-zero, it's a NaN mov r0, #0 mov r1, #0 bx lr @ return 0 for NaN /* * Convert the float in r0 to a long in r0/r1. * * We have to clip values to long min/max per the specification. The * expected common case is a "reasonable" value that converts directly * to modest integer. The EABI convert function isn't doing this for us. */ nterp_f2l_doconv: ubfx r2, r0, #23, #8 @ grab the exponent cmp r2, #0xbe @ MININT < x > MAXINT? bhs f2l_special_cases b __aeabi_f2lz @ tail call to convert float to long f2l_special_cases: cmp r2, #0xff @ NaN or infinity? beq f2l_maybeNaN f2l_notNaN: adds r0, r0, r0 @ sign bit to carry mov r0, #0xffffffff @ assume maxlong for lsw mov r1, #0x7fffffff @ assume maxlong for msw adc r0, r0, #0 adc r1, r1, #0 @ convert maxlong to minlong if exp negative bx lr @ return f2l_maybeNaN: lsls r3, r0, #9 beq f2l_notNaN @ if fraction is non-zero, it's a NaN mov r0, #0 mov r1, #0 bx lr @ return 0 for NaN // Entrypoints into runtime. NTERP_TRAMPOLINE nterp_get_static_field, NterpGetStaticField NTERP_TRAMPOLINE nterp_get_instance_field_offset, NterpGetInstanceFieldOffset NTERP_TRAMPOLINE nterp_filled_new_array, NterpFilledNewArray NTERP_TRAMPOLINE nterp_filled_new_array_range, NterpFilledNewArrayRange NTERP_TRAMPOLINE nterp_get_class_or_allocate_object, NterpGetClassOrAllocateObject NTERP_TRAMPOLINE nterp_get_method, NterpGetMethod NTERP_TRAMPOLINE nterp_hot_method, NterpHotMethod NTERP_TRAMPOLINE nterp_load_object, NterpLoadObject // gen_mterp.py will inline the following definitions // within [ExecuteNterpImpl, EndExecuteNterpImpl). %def instruction_end(): .type artNterpAsmInstructionEnd, #object .hidden artNterpAsmInstructionEnd .global artNterpAsmInstructionEnd artNterpAsmInstructionEnd: // artNterpAsmInstructionEnd is used as landing pad for exception handling. FETCH_INST GET_INST_OPCODE ip GOTO_OPCODE ip %def instruction_start(): .type artNterpAsmInstructionStart, #object .hidden artNterpAsmInstructionStart .global artNterpAsmInstructionStart artNterpAsmInstructionStart = .L_op_nop .text %def default_helper_prefix(): % return "nterp_" %def opcode_start(): NAME_START nterp_${opcode} %def opcode_end(): NAME_END nterp_${opcode} %def helper_start(name): NAME_START ${name} %def helper_end(name): NAME_END ${name}