| %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/arm64/asm_support_arm64.S" |
| |
| /** |
| * ARM64 Runtime register usage conventions. |
| * |
| * r0 : w0 is 32-bit return register and x0 is 64-bit. |
| * r0-r7 : Argument registers. |
| * r8-r15 : Caller save registers (used as temporary registers). |
| * r16-r17: Also known as ip0-ip1, respectively. Used as scratch registers by |
| * the linker, by the trampolines and other stubs (the compiler uses |
| * these as temporary registers). |
| * r18 : Reserved for platform (SCS, shadow call stack) |
| * r19 : Pointer to thread-local storage. |
| * r20-r29: Callee save registers. |
| * r30 : (lr) is reserved (the link register). |
| * rsp : (sp) is reserved (the stack pointer). |
| * rzr : (zr) is reserved (the zero register). |
| * |
| * Floating-point registers |
| * v0-v31 |
| * |
| * v0 : s0 is return register for singles (32-bit) and d0 for doubles (64-bit). |
| * This is analogous to the C/C++ (hard-float) calling convention. |
| * v0-v7 : Floating-point argument registers in both Dalvik and C/C++ conventions. |
| * Also used as temporary and codegen scratch registers. |
| * |
| * v0-v7 and v16-v31 : Caller save registers (used as temporary registers). |
| * v8-v15 : bottom 64-bits preserved across C calls (d8-d15 are preserved). |
| * |
| * v16-v31: Used as codegen temp/scratch. |
| * v8-v15 : Can be used for promotion. |
| * |
| * Must maintain 16-byte stack alignment. |
| * |
| * Nterp notes: |
| * |
| * The following registers have fixed assignments: |
| * |
| * reg nick purpose |
| * x19 xSELF self (Thread) pointer |
| * x20 wMR marking register |
| * x29 xFP interpreted frame pointer, used for accessing locals and args |
| * x22 xPC interpreted program counter, used for fetching instructions |
| * x23 xINST first 16-bit code unit of current instruction |
| * x24 xIBASE interpreted instruction base pointer, used for computed goto |
| * x25 xREFS base of object references of dex registers. |
| * x16 ip scratch reg |
| * x17 ip2 scratch reg (used by macros) |
| * |
| * Macros are provided for common operations. They MUST NOT alter unspecified registers or |
| * condition codes. |
| */ |
| |
| /* single-purpose registers, given names for clarity */ |
| #define xSELF x19 |
| #define CFI_DEX 22 // DWARF register number of the register holding dex-pc (xPC). |
| #define CFI_TMP 0 // DWARF register number of the first argument register (r0). |
| #define xPC x22 |
| #define xINST x23 |
| #define wINST w23 |
| #define xIBASE x24 |
| #define xREFS x25 |
| #define CFI_REFS 25 |
| #define ip x16 |
| #define ip2 x17 |
| #define wip w16 |
| #define wip2 w17 |
| |
| // To avoid putting ifdefs arond the use of wMR, make sure it's defined. |
| // IsNterpSupported returns false for configurations that don't have wMR (typically CMS). |
| #ifndef wMR |
| #define wMR w20 |
| #endif |
| |
| // Temporary registers while setting up a frame. |
| #define xNEW_FP x26 |
| #define xNEW_REFS x27 |
| #define CFI_NEW_REFS 27 |
| |
| // +8 for the ArtMethod of the caller. |
| #define OFFSET_TO_FIRST_ARGUMENT_IN_STACK (CALLEE_SAVES_SIZE + 8) |
| |
| /* |
| * Fetch the next instruction from xPC into wINST. Does not advance xPC. |
| */ |
| .macro FETCH_INST |
| ldrh wINST, [xPC] |
| .endm |
| |
| /* |
| * Fetch the next instruction from the specified offset. Advances xPC |
| * 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 wINST, [xPC, #((\count)*2)]! |
| .endm |
| |
| /* |
| * Similar to FETCH_ADVANCE_INST, but does not update xPC. Used to load |
| * xINST ahead of possible exception point. Be sure to manually advance xPC |
| * later. |
| */ |
| .macro PREFETCH_INST count |
| ldrh wINST, [xPC, #((\count)*2)] |
| .endm |
| |
| /* Advance xPC by some number of code units. */ |
| .macro ADVANCE count |
| add xPC, xPC, #((\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 |
| add xPC, xPC, \reg, sxtw |
| ldrh wINST, [xPC] |
| .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, [xPC, #((\count)*2)] |
| .endm |
| |
| .macro FETCH_S reg, count |
| ldrsh \reg, [xPC, #((\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, [xPC, #((\count)*2+(\byte))] |
| .endm |
| |
| /* |
| * Put the instruction's opcode field into the specified register. |
| */ |
| .macro GET_INST_OPCODE reg |
| and \reg, xINST, #255 |
| .endm |
| |
| /* |
| * Begin executing the opcode in _reg. Clobbers reg |
| */ |
| |
| .macro GOTO_OPCODE reg |
| add \reg, xIBASE, \reg, lsl #${handler_size_bits} |
| br \reg |
| .endm |
| |
| /* |
| * Get/set the 32-bit value from a Dalvik register. |
| */ |
| .macro GET_VREG reg, vreg |
| ldr \reg, [xFP, \vreg, uxtw #2] |
| .endm |
| .macro GET_VREG_OBJECT reg, vreg |
| ldr \reg, [xREFS, \vreg, uxtw #2] |
| .endm |
| .macro SET_VREG reg, vreg |
| str \reg, [xFP, \vreg, uxtw #2] |
| str wzr, [xREFS, \vreg, uxtw #2] |
| .endm |
| .macro SET_VREG_OBJECT reg, vreg, tmpreg |
| str \reg, [xFP, \vreg, uxtw #2] |
| str \reg, [xREFS, \vreg, uxtw #2] |
| .endm |
| .macro SET_VREG_FLOAT reg, vreg |
| str \reg, [xFP, \vreg, uxtw #2] |
| str wzr, [xREFS, \vreg, uxtw #2] |
| .endm |
| |
| /* |
| * Get/set the 64-bit value from a Dalvik register. |
| */ |
| .macro GET_VREG_WIDE reg, vreg |
| add ip2, xFP, \vreg, uxtw #2 |
| ldr \reg, [ip2] |
| .endm |
| .macro SET_VREG_WIDE reg, vreg |
| add ip2, xFP, \vreg, uxtw #2 |
| str \reg, [ip2] |
| add ip2, xREFS, \vreg, uxtw #2 |
| str xzr, [ip2] |
| .endm |
| .macro GET_VREG_DOUBLE reg, vreg |
| add ip2, xFP, \vreg, uxtw #2 |
| ldr \reg, [ip2] |
| .endm |
| .macro SET_VREG_DOUBLE reg, vreg |
| add ip2, xFP, \vreg, uxtw #2 |
| str \reg, [ip2] |
| add ip2, xREFS, \vreg, uxtw #2 |
| str xzr, [ip2] |
| .endm |
| |
| /* |
| * Get the 32-bit value from a Dalvik register and sign-extend to 64-bit. |
| * Used to avoid an extra instruction in int-to-long. |
| */ |
| .macro GET_VREG_S reg, vreg |
| ldrsw \reg, [xFP, \vreg, uxtw #2] |
| .endm |
| |
| // An assembly entry that has a OatQuickMethodHeader prefix. |
| .macro OAT_ENTRY name, end |
| .type \name, #function |
| .hidden \name |
| .global \name |
| .balign 16 |
| // Padding of 8 bytes to get 16 bytes alignment of code entry. |
| .long 0 |
| .long 0 |
| // OatQuickMethodHeader. |
| .long 0 |
| .long (\end - \name) |
| \name: |
| .endm |
| |
| .macro SIZE name |
| .size \name, .-\name |
| .endm |
| |
| .macro NAME_START name |
| .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 |
| 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 |
| neg \reg, \reg |
| .endm |
| |
| .macro EXPORT_PC |
| str xPC, [xREFS, #-16] |
| .endm |
| |
| .macro BRANCH |
| // Update method counter and do a suspend check if the branch is negative. |
| tbnz wINST, #31, 2f |
| 1: |
| add w2, wINST, wINST // w2<- byte offset |
| FETCH_ADVANCE_INST_RB w2 // update xPC, load wINST |
| GET_INST_OPCODE ip // extract opcode from wINST |
| GOTO_OPCODE ip // jump to next instruction |
| 2: |
| ldr x0, [sp] |
| ldrh w2, [x0, #ART_METHOD_HOTNESS_COUNT_OFFSET] |
| add x2, x2, #1 |
| and w2, w2, #NTERP_HOTNESS_MASK |
| strh w2, [x0, #ART_METHOD_HOTNESS_COUNT_OFFSET] |
| // If the counter overflows, handle this in the runtime. |
| cbz w2, NterpHandleHotnessOverflow |
| // Otherwise, do a suspend check. |
| ldr x0, [xSELF, #THREAD_FLAGS_OFFSET] |
| ands x0, x0, #THREAD_SUSPEND_OR_CHECKPOINT_REQUEST |
| b.eq 1b |
| EXPORT_PC |
| bl art_quick_test_suspend |
| b 1b |
| .endm |
| |
| // Setup the stack to start executing the method. Expects: |
| // - x0 to contain the ArtMethod |
| // |
| // Outputs |
| // - ip contains the dex registers size |
| // - x13 contains the old stack pointer. |
| // |
| // Uses ip, ip2, x13, x14 as temporaries. |
| .macro SETUP_STACK_FRAME code_item, refs, fp, cfi_refs |
| // Fetch dex register size. |
| ldrh wip, [\code_item, #CODE_ITEM_REGISTERS_SIZE_OFFSET] |
| // Fetch outs size. |
| ldrh wip2, [\code_item, #CODE_ITEM_OUTS_SIZE_OFFSET] |
| |
| // Compute required frame size: ((2 * ip) + ip1) * 4 + 24 |
| // 24 is for saving the previous frame, pc, and method being executed. |
| add x14, ip, ip |
| add x14, x14, ip2 |
| lsl x14, x14, #2 |
| add x14, x14, #24 |
| |
| // Compute new stack pointer in x14 |
| sub x14, sp, x14 |
| // Alignment |
| and x14, x14, #-16 |
| |
| // Set reference and dex registers, align to pointer size for previous frame and dex pc. |
| add \refs, x14, ip2, lsl #2 |
| add \refs, \refs, 28 |
| and \refs, \refs, #(-__SIZEOF_POINTER__) |
| add \fp, \refs, ip, lsl #2 |
| |
| // Now setup the stack pointer. |
| mov x13, sp |
| .cfi_def_cfa_register x13 |
| mov sp, x14 |
| str x13, [\refs, #-8] |
| CFI_DEF_CFA_BREG_PLUS_UCONST \cfi_refs, -8, CALLEE_SAVES_SIZE |
| |
| // Put nulls in reference frame. |
| cbz ip, 2f |
| mov ip2, \refs |
| 1: |
| str xzr, [ip2], #8 // May clear vreg[0]. |
| cmp ip2, \fp |
| b.lo 1b |
| 2: |
| // Save the ArtMethod. |
| str x0, [sp] |
| .endm |
| |
| // Increase method hotness and do suspend check before starting executing the method. |
| .macro START_EXECUTING_INSTRUCTIONS |
| ldr x0, [sp] |
| ldrh w2, [x0, #ART_METHOD_HOTNESS_COUNT_OFFSET] |
| add x2, x2, #1 |
| and w2, w2, #NTERP_HOTNESS_MASK |
| strh w2, [x0, #ART_METHOD_HOTNESS_COUNT_OFFSET] |
| // If the counter overflows, handle this in the runtime. |
| cbz w2, 2f |
| ldr x0, [xSELF, #THREAD_FLAGS_OFFSET] |
| tst x0, #THREAD_SUSPEND_OR_CHECKPOINT_REQUEST |
| b.ne 3f |
| 1: |
| FETCH_INST |
| GET_INST_OPCODE ip |
| GOTO_OPCODE ip |
| 2: |
| mov x1, xzr |
| mov x2, xFP |
| bl nterp_hot_method |
| b 1b |
| 3: |
| EXPORT_PC |
| bl art_quick_test_suspend |
| b 1b |
| .endm |
| |
| .macro SPILL_ALL_CALLEE_SAVES |
| INCREASE_FRAME CALLEE_SAVES_SIZE |
| // Note: we technically don't need to save x19 and x20, |
| // but the runtime will expect those values to be there when unwinding |
| // (see Arm64Context::DoLongJump checking for the thread register). |
| SAVE_ALL_CALLEE_SAVES 0 |
| .endm |
| |
| .macro RESTORE_ALL_CALLEE_SAVES |
| // FP callee-saves |
| ldp d8, d9, [sp, #0] |
| ldp d10, d11, [sp, #16] |
| ldp d12, d13, [sp, #32] |
| ldp d14, d15, [sp, #48] |
| |
| // GP callee-saves. |
| // No need to restore x19 (it's always the thread), and |
| // don't restore x20 (the marking register) as it may have been updated. |
| RESTORE_TWO_REGS x21, x22, 80 |
| RESTORE_TWO_REGS x23, x24, 96 |
| RESTORE_TWO_REGS x25, x26, 112 |
| RESTORE_TWO_REGS x27, x28, 128 |
| RESTORE_TWO_REGS x29, lr, 144 |
| |
| DECREASE_FRAME CALLEE_SAVES_SIZE |
| .endm |
| |
| .macro SPILL_ALL_ARGUMENTS |
| INCREASE_FRAME 128 |
| // GP arguments. |
| SAVE_TWO_REGS x0, x1, 0 |
| SAVE_TWO_REGS x2, x3, 16 |
| SAVE_TWO_REGS x4, x5, 32 |
| SAVE_TWO_REGS x6, x7, 48 |
| |
| // FP arguments |
| stp d0, d1, [sp, #64] |
| stp d2, d3, [sp, #80] |
| stp d4, d5, [sp, #96] |
| stp d6, d7, [sp, #112] |
| .endm |
| |
| .macro RESTORE_ALL_ARGUMENTS |
| // GP arguments. |
| RESTORE_TWO_REGS x0, x1, 0 |
| RESTORE_TWO_REGS x2, x3, 16 |
| RESTORE_TWO_REGS x4, x5, 32 |
| RESTORE_TWO_REGS x6, x7, 48 |
| |
| // FP arguments |
| ldp d0, d1, [sp, #64] |
| ldp d2, d3, [sp, #80] |
| ldp d4, d5, [sp, #96] |
| ldp d6, d7, [sp, #112] |
| DECREASE_FRAME 128 |
| .endm |
| |
| // Helper to setup the stack after doing a nterp to nterp call. This will setup: |
| // - xNEW_FP: the new pointer to dex registers |
| // - xNEW_REFS: the new pointer to references |
| // - xPC: the new PC pointer to execute |
| // - x2: value in instruction to decode the number of arguments. |
| // - x3: first dex register |
| // - x4: top of dex register array |
| // |
| // The method expects: |
| // - x0 to contain the ArtMethod |
| // - x8 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 x16, sp, #STACK_OVERFLOW_RESERVED_BYTES |
| ldr wzr, [x16] |
| |
| // 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 x8, xNEW_REFS, xNEW_FP, CFI_NEW_REFS |
| // Make x4 point to the top of the dex register array. |
| add x4, xNEW_FP, ip, uxtx #2 |
| |
| // Fetch instruction information before replacing xPC. |
| // TODO: move this down to the method that uses it, fetching it directly from wINST. |
| FETCH_B w2, 0, 1 |
| // TODO: we could avoid this as instance invokes already fetch it. |
| FETCH w3, 2 |
| |
| // Set the dex pc pointer. |
| add xPC, x8, #CODE_ITEM_INSNS_OFFSET |
| 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: |
| // - xNEW_FP: the new pointer to dex registers |
| // - xNEW_REFS: the new pointer to references |
| // - xPC: the new PC pointer to execute |
| // - x2: number of arguments (bits 4-7), 5th argument if any (bits 0-3) |
| // - x3: first dex register |
| // - x4: top of dex register array |
| // - x1: receiver if non-static. |
| .macro SETUP_NON_RANGE_ARGUMENTS_AND_EXECUTE is_static=0, is_string_init=0 |
| // /* op vA, vB, {vC...vG} */ |
| asr ip2, x2, #4 |
| cbz ip2, 6f |
| mov ip, #-4 |
| cmp ip2, #2 |
| b.lt 1f |
| b.eq 2f |
| cmp ip2, #4 |
| b.lt 3f |
| b.eq 4f |
| |
| // We use a decrementing ip to store references relative |
| // to xNEW_FP and dex registers relative to x4 |
| // |
| // TODO: We could set up ip 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 x2, x2, #15 |
| GET_VREG_OBJECT w5, w2 |
| str w5, [xNEW_FP, ip] |
| GET_VREG w5, w2 |
| str w5, [x4, ip] |
| sub ip, ip, #4 |
| 4: |
| asr x2, x3, #12 |
| GET_VREG_OBJECT w5, w2 |
| str w5, [xNEW_FP, ip] |
| GET_VREG w5, w2 |
| str w5, [x4, ip] |
| sub ip, ip, #4 |
| 3: |
| ubfx x2, x3, #8, #4 |
| GET_VREG_OBJECT w5, w2 |
| str w5, [xNEW_FP, ip] |
| GET_VREG w5, w2 |
| str w5, [x4, ip] |
| sub ip, ip, #4 |
| 2: |
| ubfx x2, x3, #4, #4 |
| GET_VREG_OBJECT w5, w2 |
| str w5, [xNEW_FP, ip] |
| GET_VREG w5, w2 |
| str w5, [x4, ip] |
| .if !\is_string_init |
| sub ip, ip, #4 |
| .endif |
| 1: |
| .if \is_string_init |
| // Ignore the first argument |
| .elseif \is_static |
| and x2, x3, #0xf |
| GET_VREG_OBJECT w5, w2 |
| str w5, [xNEW_FP, ip] |
| GET_VREG w5, w2 |
| str w5, [x4, ip] |
| .else |
| str w1, [xNEW_FP, ip] |
| str w1, [x4, ip] |
| .endif |
| |
| 6: |
| // Start executing the method. |
| mov xFP, xNEW_FP |
| mov xREFS, xNEW_REFS |
| CFI_DEF_CFA_BREG_PLUS_UCONST CFI_REFS, -8, CALLEE_SAVES_SIZE |
| START_EXECUTING_INSTRUCTIONS |
| .endm |
| |
| // Setup arguments based on a range nterp to nterp call, and start executing |
| // the method. |
| // - xNEW_FP: the new pointer to dex registers |
| // - xNEW_REFS: the new pointer to references |
| // - xPC: the new PC pointer to execute |
| // - x2: number of arguments |
| // - x3: first dex register |
| // - x4: top of dex register array |
| // - x1: receiver if non-static. |
| // |
| // Uses ip, ip2, x5, x6 as temporaries. |
| .macro SETUP_RANGE_ARGUMENTS_AND_EXECUTE is_static=0, is_string_init=0 |
| mov ip, #-4 |
| .if \is_string_init |
| // Ignore the first argument |
| sub x2, x2, #1 |
| add x3, x3, #1 |
| .elseif !\is_static |
| sub x2, x2, #1 |
| add x3, x3, #1 |
| .endif |
| |
| cbz x2, 2f |
| add ip2, xREFS, x3, lsl #2 // pointer to first argument in reference array |
| add ip2, ip2, x2, lsl #2 // pointer to last argument in reference array |
| add x5, xFP, x3, lsl #2 // pointer to first argument in register array |
| add x6, x5, x2, lsl #2 // pointer to last argument in register array |
| 1: |
| ldr w7, [ip2, #-4]! |
| str w7, [xNEW_FP, ip] |
| sub x2, x2, 1 |
| ldr w7, [x6, #-4]! |
| str w7, [x4, ip] |
| sub ip, ip, 4 |
| cbnz x2, 1b |
| 2: |
| .if \is_string_init |
| // Ignore first argument |
| .elseif !\is_static |
| str w1, [xNEW_FP, ip] |
| str w1, [x4, ip] |
| .endif |
| mov xFP, xNEW_FP |
| mov xREFS, xNEW_REFS |
| CFI_DEF_CFA_BREG_PLUS_UCONST CFI_REFS, -8, CALLEE_SAVES_SIZE |
| START_EXECUTING_INSTRUCTIONS |
| .endm |
| |
| .macro GET_SHORTY dest, is_interface, is_polymorphic, is_custom |
| stp x0, x1, [sp, #-16]! |
| .if \is_polymorphic |
| ldr x0, [sp, #16] |
| mov x1, xPC |
| bl NterpGetShortyFromInvokePolymorphic |
| .elseif \is_custom |
| ldr x0, [sp, #16] |
| mov x1, xPC |
| bl NterpGetShortyFromInvokeCustom |
| .elseif \is_interface |
| ldr x0, [sp, #16] |
| FETCH w1, 1 |
| bl NterpGetShortyFromMethodId |
| .else |
| bl NterpGetShorty |
| .endif |
| mov \dest, x0 |
| ldp x0, x1, [sp], #16 |
| .endm |
| |
| // Output: x8 contains the code item |
| .macro GET_CODE_ITEM |
| // TODO: Get code item in a better way. |
| stp x0, x1, [sp, #-16]! |
| bl NterpGetCodeItem |
| mov x8, x0 |
| ldp x0, x1, [sp], #16 |
| .endm |
| |
| .macro DO_ENTRY_POINT_CHECK call_compiled_code |
| // On entry, the method is x0, the instance is x1 |
| adr x2, ExecuteNterpImpl |
| ldr x3, [x0, #ART_METHOD_QUICK_CODE_OFFSET_64] |
| cmp x2, x3 |
| b.ne \call_compiled_code |
| .endm |
| |
| .macro UPDATE_REGISTERS_FOR_STRING_INIT old_value, new_value |
| mov wip, wzr |
| 1: |
| GET_VREG_OBJECT wip2, wip |
| cmp wip2, \old_value |
| b.ne 2f |
| SET_VREG_OBJECT \new_value, wip |
| 2: |
| add wip, wip, #1 |
| add ip2, xREFS, wip, uxtw #2 |
| cmp ip2, xFP |
| b.ne 1b |
| .endm |
| |
| // Puts the next floating point argument into the expected register, |
| // fetching values based on a non-range invoke. |
| // Uses ip and ip2. |
| .macro LOOP_OVER_SHORTY_LOADING_FPS dreg, sreg, inst, shorty, arg_index, finished |
| 1: // LOOP |
| ldrb wip, [\shorty], #1 // Load next character in shorty, and increment. |
| cbz wip, \finished // if (wip == '\0') goto finished |
| cmp wip, #68 // if (wip == 'D') goto FOUND_DOUBLE |
| b.eq 2f |
| cmp wip, #70 // if (wip == 'F') goto FOUND_FLOAT |
| b.eq 3f |
| lsr \inst, \inst, #4 |
| add \arg_index, \arg_index, #1 |
| // Handle extra argument in arg array taken by a long. |
| cmp wip, #74 // if (wip != 'J') goto LOOP |
| b.ne 1b |
| lsr \inst, \inst, #4 |
| add \arg_index, \arg_index, #1 |
| b 1b // goto LOOP |
| 2: // FOUND_DOUBLE |
| and ip, \inst, #0xf |
| GET_VREG wip, wip |
| lsr \inst, \inst, #4 |
| add \arg_index, \arg_index, #1 |
| cmp \arg_index, #4 |
| b.eq 5f |
| and ip2, \inst, #0xf |
| lsr \inst, \inst, #4 |
| add \arg_index, \arg_index, #1 |
| b 6f |
| 5: |
| // TODO: Extract from wINST here and below? (Requires using a different register |
| // in the COMMON_INVOKE_NON_RANGE.) |
| FETCH_B wip2, 0, 1 |
| and wip2, wip2, #0xf |
| 6: |
| GET_VREG wip2, wip2 |
| add ip, ip, ip2, lsl #32 |
| fmov \dreg, ip |
| b 4f |
| 3: // FOUND_FLOAT |
| cmp \arg_index, #4 |
| b.eq 7f |
| and ip, \inst, #0xf |
| lsr \inst, \inst, #4 |
| add \arg_index, \arg_index, #1 |
| b 8f |
| 7: |
| FETCH_B wip, 0, 1 |
| and wip, wip, #0xf |
| 8: |
| GET_VREG \sreg, wip |
| 4: |
| .endm |
| |
| // Puts the next int/long/object argument in the expected register, |
| // fetching values based on a non-range invoke. |
| // Uses ip and ip2. |
| .macro LOOP_OVER_SHORTY_LOADING_GPRS gpr_reg64, gpr_reg32, inst, shorty, arg_index, finished |
| 1: // LOOP |
| ldrb wip, [\shorty], #1 // Load next character in shorty, and increment. |
| cbz wip, \finished // if (wip == '\0') goto finished |
| cmp wip, #74 // if (wip == 'J') goto FOUND_LONG |
| b.eq 2f |
| cmp wip, #70 // if (wip == 'F') goto SKIP_FLOAT |
| b.eq 3f |
| cmp wip, #68 // if (wip == 'D') goto SKIP_DOUBLE |
| b.eq 4f |
| cmp \arg_index, #4 |
| b.eq 7f |
| and ip, \inst, #0xf |
| lsr \inst, \inst, #4 |
| add \arg_index, \arg_index, #1 |
| b 8f |
| 7: |
| FETCH_B wip, 0, 1 |
| and wip, wip, #0xf |
| 8: |
| GET_VREG \gpr_reg32, wip |
| b 5f |
| 2: // FOUND_LONG |
| and ip, \inst, #0xf |
| GET_VREG wip, wip |
| lsr \inst, \inst, #4 |
| add \arg_index, \arg_index, #1 |
| cmp \arg_index, #4 |
| b.eq 9f |
| and ip2, \inst, #0xf |
| lsr \inst, \inst, #4 |
| add \arg_index, \arg_index, #1 |
| b 10f |
| 9: |
| FETCH_B wip2, 0, 1 |
| and wip2, wip2, #0xf |
| 10: |
| GET_VREG wip2, wip2 |
| add \gpr_reg64, ip, ip2, lsl #32 |
| b 5f |
| 3: // SKIP_FLOAT |
| lsr \inst, \inst, #4 |
| add \arg_index, \arg_index, #1 |
| b 1b |
| 4: // SKIP_DOUBLE |
| lsr \inst, \inst, #4 |
| add \arg_index, \arg_index, #1 |
| cmp \arg_index, #4 |
| b.eq 1b |
| lsr \inst, \inst, #4 |
| add \arg_index, \arg_index, #1 |
| b 1b |
| 5: |
| .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 |
| 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 |
| .endif |
| |
| .Lcall_compiled_code_\suffix: |
| GET_SHORTY xINST, \is_interface, \is_polymorphic, \is_custom |
| // From this point: |
| // - xINST contains shorty (in callee-save to switch over return value after call). |
| // - x0 contains method |
| // - x1 contains 'this' pointer for instance method. |
| add x9, xINST, #1 // shorty + 1 ; ie skip return arg character |
| FETCH w11, 2 // arguments |
| .if \is_string_init |
| lsr x11, x11, #4 |
| mov x10, #1 // ignore first argument |
| .elseif \is_static |
| mov x10, xzr // arg_index |
| .else |
| lsr x11, x11, #4 |
| mov x10, #1 // ignore first argument |
| .endif |
| LOOP_OVER_SHORTY_LOADING_FPS d0, s0, x11, x9, x10, .Lxmm_setup_finished_\suffix |
| LOOP_OVER_SHORTY_LOADING_FPS d1, s1, x11, x9, x10, .Lxmm_setup_finished_\suffix |
| LOOP_OVER_SHORTY_LOADING_FPS d2, s2, x11, x9, x10, .Lxmm_setup_finished_\suffix |
| LOOP_OVER_SHORTY_LOADING_FPS d3, s3, x11, x9, x10, .Lxmm_setup_finished_\suffix |
| LOOP_OVER_SHORTY_LOADING_FPS d4, s4, x11, x9, x10, .Lxmm_setup_finished_\suffix |
| .Lxmm_setup_finished_\suffix: |
| add x9, xINST, #1 // shorty + 1 ; ie skip return arg character |
| FETCH w11, 2 // arguments |
| .if \is_string_init |
| lsr x11, x11, #4 |
| mov x10, #1 // ignore first argument |
| LOOP_OVER_SHORTY_LOADING_GPRS x1, w1, x11, x9, x10, .Lgpr_setup_finished_\suffix |
| .elseif \is_static |
| mov x10, xzr // arg_index |
| LOOP_OVER_SHORTY_LOADING_GPRS x1, w1, x11, x9, x10, .Lgpr_setup_finished_\suffix |
| .else |
| lsr x11, x11, #4 |
| mov x10, #1 // ignore first argument |
| .endif |
| LOOP_OVER_SHORTY_LOADING_GPRS x2, w2, x11, x9, x10, .Lgpr_setup_finished_\suffix |
| LOOP_OVER_SHORTY_LOADING_GPRS x3, w3, x11, x9, x10, .Lgpr_setup_finished_\suffix |
| LOOP_OVER_SHORTY_LOADING_GPRS x4, w4, x11, x9, x10, .Lgpr_setup_finished_\suffix |
| LOOP_OVER_SHORTY_LOADING_GPRS x5, w5, x11, x9, x10, .Lgpr_setup_finished_\suffix |
| .Lgpr_setup_finished_\suffix: |
| .if \is_polymorphic |
| bl art_quick_invoke_polymorphic |
| .elseif \is_custom |
| bl art_quick_invoke_custom |
| .else |
| .if \is_interface |
| // Setup hidden argument |
| FETCH wip2, 1 |
| .endif |
| ldr lr, [x0, #ART_METHOD_QUICK_CODE_OFFSET_64] |
| blr lr |
| .endif |
| ldrb wip, [xINST] |
| cmp ip, #68 // Test if result type char == 'D'. |
| b.eq .Lreturn_double_\suffix |
| cmp ip, #70 |
| b.ne .Ldone_return_\suffix |
| .Lreturn_float_\suffix: |
| fmov w0, s0 |
| b .Ldone_return_\suffix |
| .Lreturn_double_\suffix: |
| fmov x0, d0 |
| .Ldone_return_\suffix: |
| /* resume execution of caller */ |
| .if \is_string_init |
| FETCH w11, 2 // arguments |
| and x11, x11, #0xf |
| GET_VREG w1, w11 |
| UPDATE_REGISTERS_FOR_STRING_INIT w1, w0 |
| .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 floating point argument into the expected register, |
| // fetching values based on a range invoke. |
| // Uses ip as temporary. |
| .macro LOOP_RANGE_OVER_SHORTY_LOADING_FPS dreg, sreg, shorty, arg_index, stack_index, finished |
| 1: // LOOP |
| ldrb wip, [\shorty], #1 // Load next character in shorty, and increment. |
| cbz wip, \finished // if (wip == '\0') goto finished |
| cmp wip, #68 // if (wip == 'D') goto FOUND_DOUBLE |
| b.eq 2f |
| cmp wip, #70 // if (wip == 'F') goto FOUND_FLOAT |
| b.eq 3f |
| add \arg_index, \arg_index, #1 |
| add \stack_index, \stack_index, #1 |
| // Handle extra argument in arg array taken by a long. |
| cmp wip, #74 // if (wip != 'J') goto LOOP |
| b.ne 1b |
| add \arg_index, \arg_index, #1 |
| add \stack_index, \stack_index, #1 |
| b 1b // goto LOOP |
| 2: // FOUND_DOUBLE |
| GET_VREG_DOUBLE \dreg, \arg_index |
| add \arg_index, \arg_index, #2 |
| add \stack_index, \stack_index, #2 |
| b 4f |
| 3: // FOUND_FLOAT |
| GET_VREG \sreg, \arg_index |
| add \arg_index, \arg_index, #1 |
| add \stack_index, \stack_index, #1 |
| 4: |
| .endm |
| |
| // Puts the next floating point argument into the expected stack slot, |
| // fetching values based on a range invoke. |
| // Uses ip as temporary. |
| // |
| // TODO: We could just copy all the vregs to the stack slots in a simple loop |
| // without looking at the shorty at all. (We could also drop |
| // the "stack_index" from the macros for loading registers.) We could also do |
| // that conditionally if argument word count > 6; otherwise we know that all |
| // args fit into registers. |
| .macro LOOP_RANGE_OVER_FPs shorty, arg_index, stack_index, finished |
| 1: // LOOP |
| ldrb wip, [\shorty], #1 // Load next character in shorty, and increment. |
| cbz wip, \finished // if (wip == '\0') goto finished |
| cmp wip, #68 // if (wip == 'D') goto FOUND_DOUBLE |
| b.eq 2f |
| cmp wip, #70 // if (wip == 'F') goto FOUND_FLOAT |
| b.eq 3f |
| add \arg_index, \arg_index, #1 |
| add \stack_index, \stack_index, #1 |
| // Handle extra argument in arg array taken by a long. |
| cmp wip, #74 // if (wip != 'J') goto LOOP |
| b.ne 1b |
| add \arg_index, \arg_index, #1 |
| add \stack_index, \stack_index, #1 |
| b 1b // goto LOOP |
| 2: // FOUND_DOUBLE |
| GET_VREG_WIDE ip, \arg_index |
| add ip2, sp, \stack_index, uxtw #2 |
| str ip, [ip2] |
| add \arg_index, \arg_index, #2 |
| add \stack_index, \stack_index, #2 |
| b 1b |
| 3: // FOUND_FLOAT |
| GET_VREG wip, \arg_index |
| str wip, [sp, \stack_index, uxtw #2] |
| add \arg_index, \arg_index, #1 |
| add \stack_index, \stack_index, #1 |
| b 1b |
| .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 reg64, reg32, shorty, arg_index, stack_index, finished |
| 1: // LOOP |
| ldrb wip, [\shorty], #1 // Load next character in shorty, and increment. |
| cbz wip, \finished // if (wip == '\0') goto finished |
| cmp wip, #74 // if (wip == 'J') goto FOUND_LONG |
| b.eq 2f |
| cmp wip, #70 // if (wip == 'F') goto SKIP_FLOAT |
| b.eq 3f |
| cmp wip, #68 // if (wip == 'D') goto SKIP_DOUBLE |
| b.eq 4f |
| GET_VREG \reg32, \arg_index |
| add \arg_index, \arg_index, #1 |
| add \stack_index, \stack_index, #1 |
| b 5f |
| 2: // FOUND_LONG |
| GET_VREG_WIDE \reg64, \arg_index |
| add \arg_index, \arg_index, #2 |
| add \stack_index, \stack_index, #2 |
| b 5f |
| 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 wip, [\shorty], #1 // Load next character in shorty, and increment. |
| cbz wip, \finished // if (wip == '\0') goto finished |
| cmp wip, #74 // if (wip == 'J') goto FOUND_LONG |
| b.eq 2f |
| cmp wip, #70 // if (wip == 'F') goto SKIP_FLOAT |
| b.eq 3f |
| cmp wip, #68 // if (wip == 'D') goto SKIP_DOUBLE |
| b.eq 4f |
| GET_VREG wip, \arg_index |
| str wip, [sp, \stack_index, uxtw #2] |
| add \arg_index, \arg_index, #1 |
| add \stack_index, \stack_index, #1 |
| b 1b |
| 2: // FOUND_LONG |
| GET_VREG_WIDE ip, \arg_index |
| add ip2, sp, \stack_index, uxtw #2 |
| str ip, [ip2] |
| add \arg_index, \arg_index, #2 |
| add \stack_index, \stack_index, #2 |
| 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 |
| 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 |
| .endif |
| |
| .Lcall_compiled_code_range_\suffix: |
| GET_SHORTY xINST, \is_interface, \is_polymorphic, \is_custom |
| // From this point: |
| // - xINST contains shorty (in callee-save to switch over return value after call). |
| // - x0 contains method |
| // - x1 contains 'this' pointer for instance method. |
| add x9, xINST, #1 // shorty + 1 ; ie skip return arg character |
| FETCH w10, 2 // arguments |
| .if \is_string_init |
| add x10, x10, #1 // arg start index |
| mov x11, #1 // index in stack |
| .elseif \is_static |
| mov x11, xzr // index in stack |
| .else |
| add x10, x10, #1 // arg start index |
| mov x11, #1 // index in stack |
| .endif |
| LOOP_RANGE_OVER_SHORTY_LOADING_FPS d0, s0, x9, w10, w11, .Lxmm_setup_finished_range_\suffix |
| LOOP_RANGE_OVER_SHORTY_LOADING_FPS d1, s1, x9, w10, w11, .Lxmm_setup_finished_range_\suffix |
| LOOP_RANGE_OVER_SHORTY_LOADING_FPS d2, s2, x9, w10, w11, .Lxmm_setup_finished_range_\suffix |
| LOOP_RANGE_OVER_SHORTY_LOADING_FPS d3, s3, x9, w10, w11, .Lxmm_setup_finished_range_\suffix |
| LOOP_RANGE_OVER_SHORTY_LOADING_FPS d4, s4, x9, w10, w11, .Lxmm_setup_finished_range_\suffix |
| LOOP_RANGE_OVER_SHORTY_LOADING_FPS d5, s5, x9, w10, w11, .Lxmm_setup_finished_range_\suffix |
| LOOP_RANGE_OVER_SHORTY_LOADING_FPS d6, s6, x9, w10, w11, .Lxmm_setup_finished_range_\suffix |
| LOOP_RANGE_OVER_SHORTY_LOADING_FPS d7, s7, x9, w10, w11, .Lxmm_setup_finished_range_\suffix |
| // Store in the outs array (stored above the ArtMethod in the stack) |
| add x11, x11, #2 // Add two words for the ArtMethod stored before the outs. |
| LOOP_RANGE_OVER_FPs x9, w10, w11, .Lxmm_setup_finished_range_\suffix |
| .Lxmm_setup_finished_range_\suffix: |
| add x9, xINST, #1 // shorty + 1 ; ie skip return arg character |
| FETCH w10, 2 // arguments |
| .if \is_string_init |
| add x10, x10, #1 // arg start index |
| mov x11, #1 // index in stack |
| LOOP_RANGE_OVER_SHORTY_LOADING_GPRS x1, w1, x9, w10, w11, .Lgpr_setup_finished_\suffix |
| .elseif \is_static |
| mov x11, xzr // index in stack |
| LOOP_RANGE_OVER_SHORTY_LOADING_GPRS x1, w1, x9, w10, w11 .Lgpr_setup_finished_\suffix |
| .else |
| add x10, x10, #1 // arg start index |
| mov x11, #1 // index in stack |
| .endif |
| LOOP_RANGE_OVER_SHORTY_LOADING_GPRS x2, w2, x9, w10, w11, .Lgpr_setup_finished_range_\suffix |
| LOOP_RANGE_OVER_SHORTY_LOADING_GPRS x3, w3, x9, w10, w11, .Lgpr_setup_finished_range_\suffix |
| LOOP_RANGE_OVER_SHORTY_LOADING_GPRS x4, w4, x9, w10, w11, .Lgpr_setup_finished_range_\suffix |
| LOOP_RANGE_OVER_SHORTY_LOADING_GPRS x5, w5, x9, w10, w11, .Lgpr_setup_finished_range_\suffix |
| LOOP_RANGE_OVER_SHORTY_LOADING_GPRS x6, w6, x9, w10, w11, .Lgpr_setup_finished_range_\suffix |
| LOOP_RANGE_OVER_SHORTY_LOADING_GPRS x7, w7, x9, w10, w11, .Lgpr_setup_finished_range_\suffix |
| // Store in the outs array (stored above the ArtMethod in the stack) |
| add x11, x11, #2 // Add two words for the ArtMethod stored before the outs. |
| LOOP_RANGE_OVER_INTs x9, w10, w11, .Lgpr_setup_finished_range_\suffix |
| .Lgpr_setup_finished_range_\suffix: |
| .if \is_polymorphic |
| bl art_quick_invoke_polymorphic |
| .elseif \is_custom |
| bl art_quick_invoke_custom |
| .else |
| .if \is_interface |
| // Setup hidden argument |
| FETCH wip2, 1 |
| .endif |
| ldr lr, [x0, #ART_METHOD_QUICK_CODE_OFFSET_64] |
| blr lr |
| .endif |
| ldrb wip, [xINST] |
| cmp ip, #68 // Test if result type char == 'D'. |
| b.eq .Lreturn_double_range_\suffix |
| cmp ip, #70 |
| b.ne .Ldone_return_\suffix |
| .Lreturn_float_range_\suffix: |
| fmov w0, s0 |
| b .Ldone_return_range_\suffix |
| .Lreturn_double_range_\suffix: |
| fmov x0, d0 |
| .Ldone_return_range_\suffix: |
| /* resume execution of caller */ |
| .if \is_string_init |
| FETCH w11, 2 // arguments |
| GET_VREG w1, w11 |
| UPDATE_REGISTERS_FOR_STRING_INIT w1, w0 |
| .endif |
| |
| .if \is_polymorphic |
| FETCH_ADVANCE_INST 4 |
| .else |
| FETCH_ADVANCE_INST 3 |
| .endif |
| GET_INST_OPCODE ip |
| GOTO_OPCODE ip |
| .endm |
| |
| // Fetch some information from the thread cache. |
| // Uses ip and ip2 as temporaries. |
| .macro FETCH_FROM_THREAD_CACHE dest_reg, slow_path |
| add ip, xSELF, #THREAD_INTERPRETER_CACHE_OFFSET // cache address |
| ubfx ip2, xPC, #2, #THREAD_INTERPRETER_CACHE_SIZE_LOG2 // entry index |
| add ip, ip, ip2, lsl #4 // entry address within the cache |
| ldp ip, \dest_reg, [ip] // entry key (pc) and value (offset) |
| cmp ip, xPC |
| b.ne \slow_path |
| .endm |
| |
| // Helper for static field get. |
| .macro OP_SGET load="ldr", volatile_load="ldar", maybe_extend="", wide="0" |
| // Fast-path which gets the field from thread-local cache. |
| FETCH_FROM_THREAD_CACHE x0, 4f |
| 1: |
| ldr w1, [x0, #ART_FIELD_OFFSET_OFFSET] |
| ldr w0, [x0, #ART_FIELD_DECLARING_CLASS_OFFSET] |
| cbnz wMR, 3f |
| 2: |
| lsr w2, wINST, #8 // w2 <- A |
| .if \wide |
| ldr x0, [x0, x1] |
| SET_VREG_WIDE x0, w2 // fp[A] <- value |
| .else |
| \load w0, [x0, x1] |
| SET_VREG w0, w2 // fp[A] <- value |
| .endif |
| FETCH_ADVANCE_INST 2 |
| GET_INST_OPCODE ip |
| GOTO_OPCODE ip |
| 3: |
| bl art_quick_read_barrier_mark_reg00 |
| b 2b |
| 4: |
| mov x0, xSELF |
| ldr x1, [sp] |
| mov x2, xPC |
| EXPORT_PC |
| bl nterp_get_static_field |
| tbz x0, #0, 1b |
| CLEAR_STATIC_VOLATILE_MARKER x0 |
| ldr w1, [x0, #ART_FIELD_OFFSET_OFFSET] |
| ldr w0, [x0, #ART_FIELD_DECLARING_CLASS_OFFSET] |
| cbnz wMR, 7f |
| 5: |
| lsr w2, wINST, #8 // w2 <- A |
| add x0, x0, x1 |
| .if \wide |
| ldar x0, [x0] |
| SET_VREG_WIDE x0, w2 // fp[A] <- value |
| .else |
| \volatile_load w0, [x0] |
| \maybe_extend |
| SET_VREG w0, w2 // fp[A] <- value |
| .endif |
| FETCH_ADVANCE_INST 2 |
| GET_INST_OPCODE ip |
| GOTO_OPCODE ip |
| 7: |
| bl art_quick_read_barrier_mark_reg00 |
| b 5b |
| .endm |
| |
| // Helper for static field put. |
| .macro OP_SPUT store="str", volatile_store="stlr", wide="0": |
| // Fast-path which gets the field from thread-local cache. |
| FETCH_FROM_THREAD_CACHE x0, 4f |
| 1: |
| ldr w1, [x0, #ART_FIELD_OFFSET_OFFSET] |
| ldr w0, [x0, #ART_FIELD_DECLARING_CLASS_OFFSET] |
| cbnz wMR, 3f |
| 2: |
| lsr w2, wINST, #8 // w2 <- A |
| .if \wide |
| GET_VREG_WIDE x2, w2 // x2 <- v[A] |
| \store x2, [x0, x1] |
| .else |
| GET_VREG w2, w2 // w2 <- v[A] |
| \store w2, [x0, x1] |
| .endif |
| FETCH_ADVANCE_INST 2 |
| GET_INST_OPCODE ip |
| GOTO_OPCODE ip |
| 3: |
| bl art_quick_read_barrier_mark_reg00 |
| b 2b |
| 4: |
| mov x0, xSELF |
| ldr x1, [sp] |
| mov x2, xPC |
| EXPORT_PC |
| bl nterp_get_static_field |
| tbz x0, #0, 1b |
| CLEAR_STATIC_VOLATILE_MARKER x0 |
| ldr w1, [x0, #ART_FIELD_OFFSET_OFFSET] |
| ldr w0, [x0, #ART_FIELD_DECLARING_CLASS_OFFSET] |
| cbnz wMR, 6f |
| 5: |
| lsr w2, wINST, #8 // w2 <- A |
| add x0, x0, x1 |
| .if \wide |
| GET_VREG_WIDE x2, w2 // x2 <- v[A] |
| \volatile_store x2, [x0] |
| .else |
| GET_VREG w2, w2 // w2 <- v[A] |
| \volatile_store w2, [x0] |
| .endif |
| FETCH_ADVANCE_INST 2 |
| GET_INST_OPCODE ip |
| GOTO_OPCODE ip |
| 6: |
| bl art_quick_read_barrier_mark_reg00 |
| b 5b |
| .endm |
| |
| |
| // Helper for instance field put. |
| .macro OP_IPUT store="str", volatile_store="stlr", wide="0": |
| // Fast-path which gets the field from thread-local cache. |
| FETCH_FROM_THREAD_CACHE x0, 2f |
| 1: |
| ubfx w1, wINST, #8, #4 // w1<- A |
| lsr w2, wINST, #12 // w2<- B |
| GET_VREG w2, w2 // vB (object we're operating on) |
| cbz w2, common_errNullObject |
| .if \wide |
| GET_VREG_WIDE x1, w1 // x1<- fp[A]/fp[A+1] |
| \store x1, [x2, x0] |
| .else |
| GET_VREG w1, w1 // w1 <- v[A] |
| \store w1, [x2, x0] |
| .endif |
| FETCH_ADVANCE_INST 2 |
| GET_INST_OPCODE ip |
| GOTO_OPCODE ip |
| 2: |
| mov x0, xSELF |
| ldr x1, [sp] |
| mov x2, xPC |
| EXPORT_PC |
| bl nterp_get_instance_field_offset |
| tbz w0, #31, 1b |
| CLEAR_INSTANCE_VOLATILE_MARKER w0 |
| ubfx w1, wINST, #8, #4 // w1<- A |
| lsr w2, wINST, #12 // w2<- B |
| GET_VREG w2, w2 // vB (object we're operating on) |
| cbz w2, common_errNullObject |
| add x2, x2, x0 |
| .if \wide |
| GET_VREG_WIDE x1, w1 // x1<- fp[A]/fp[A+1] |
| \volatile_store x1, [x2] |
| .else |
| GET_VREG w1, w1 // w1 <- v[A] |
| \volatile_store w1, [x2] |
| .endif |
| FETCH_ADVANCE_INST 2 |
| GET_INST_OPCODE ip |
| GOTO_OPCODE ip |
| .endm |
| |
| // Helper for instance field get. |
| .macro OP_IGET load="ldr", volatile_load="ldar", maybe_extend="", wide="0" |
| // Fast-path which gets the field from thread-local cache. |
| FETCH_FROM_THREAD_CACHE x0, 2f |
| 1: |
| lsr w2, wINST, #12 // w2<- B |
| GET_VREG w3, w2 // w3<- object we're operating on |
| ubfx w2, wINST, #8, #4 // w2<- A |
| cbz w3, common_errNullObject // object was null |
| .if \wide |
| \load x0, [x3, x0] |
| SET_VREG_WIDE x0, w2 // fp[A] <- value |
| .else |
| \load w0, [x3, x0] |
| SET_VREG w0, w2 // fp[A] <- value |
| .endif |
| FETCH_ADVANCE_INST 2 |
| GET_INST_OPCODE ip |
| GOTO_OPCODE ip |
| 2: |
| mov x0, xSELF |
| ldr x1, [sp] |
| mov x2, xPC |
| EXPORT_PC |
| bl nterp_get_instance_field_offset |
| tbz w0, #31, 1b |
| CLEAR_INSTANCE_VOLATILE_MARKER w0 |
| lsr w2, wINST, #12 // w2<- B |
| GET_VREG w3, w2 // w3<- object we're operating on |
| ubfx w2, wINST, #8, #4 // w2<- A |
| cbz w3, common_errNullObject // object was null |
| add x3, x3, x0 |
| .if \wide |
| \volatile_load x0, [x3] |
| SET_VREG_WIDE x0, w2 // fp[A] <- value |
| .else |
| \volatile_load w0, [x3] |
| \maybe_extend |
| SET_VREG w0, w2 // fp[A] <- value |
| .endif |
| FETCH_ADVANCE_INST 2 |
| GET_INST_OPCODE ip |
| GOTO_OPCODE ip |
| .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. |
| .macro LOOP_OVER_SHORTY_STORING_GPRS gpr_64, gpr_32, shorty, arg_offset, regs, refs, finished |
| 1: // LOOP |
| ldrb wip, [\shorty], #1 // Load next character in shorty, and increment. |
| cbz wip, \finished // if (wip == '\0') goto finished |
| cmp wip, #74 // if (wip == 'J') goto FOUND_LONG |
| b.eq 2f |
| cmp wip, #70 // if (wip == 'F') goto SKIP_FLOAT |
| b.eq 3f |
| cmp wip, #68 // if (wip == 'D') goto SKIP_DOUBLE |
| b.eq 4f |
| str \gpr_32, [\regs, \arg_offset] |
| cmp wip, #76 // if (wip != 'L') goto NOT_REFERENCE |
| b.ne 6f |
| str \gpr_32, [\refs, \arg_offset] |
| 6: // NOT_REFERENCE |
| add \arg_offset, \arg_offset, #4 |
| b 5f |
| 2: // FOUND_LONG |
| str \gpr_64, [\regs, \arg_offset] |
| add \arg_offset, \arg_offset, #8 |
| b 5f |
| 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 floating point parameter passed in physical register |
| // in the expected dex register array entry. |
| // Uses ip as temporary. |
| .macro LOOP_OVER_SHORTY_STORING_FPS dreg, sreg, shorty, arg_offset, fp, finished |
| 1: // LOOP |
| ldrb wip, [\shorty], #1 // Load next character in shorty, and increment. |
| cbz wip, \finished // if (wip == '\0') goto finished |
| cmp wip, #68 // if (wip == 'D') goto FOUND_DOUBLE |
| b.eq 2f |
| cmp wip, #70 // if (wip == 'F') goto FOUND_FLOAT |
| b.eq 3f |
| add \arg_offset, \arg_offset, #4 |
| // Handle extra argument in arg array taken by a long. |
| cmp wip, #74 // if (wip != 'J') goto LOOP |
| b.ne 1b |
| add \arg_offset, \arg_offset, #4 |
| b 1b // goto LOOP |
| 2: // FOUND_DOUBLE |
| str \dreg, [\fp, \arg_offset] |
| add \arg_offset, \arg_offset, #8 |
| b 4f |
| 3: // FOUND_FLOAT |
| str \sreg, [\fp, \arg_offset] |
| add \arg_offset, \arg_offset, #4 |
| 4: |
| .endm |
| |
| // Puts the next floating point parameter passed in stack |
| // in the expected dex register array entry. |
| // Uses ip as temporary. |
| // |
| // TODO: Or we could just spill regs to the reserved slots in the caller's |
| // frame and copy all regs in a simple loop. This time, however, we would |
| // need to look at the shorty anyway to look for the references. |
| // (The trade-off is different for passing arguments and receiving them.) |
| .macro LOOP_OVER_FPs shorty, arg_offset, regs, stack_ptr, finished |
| 1: // LOOP |
| ldrb wip, [\shorty], #1 // Load next character in shorty, and increment. |
| cbz wip, \finished // if (wip == '\0') goto finished |
| cmp wip, #68 // if (wip == 'D') goto FOUND_DOUBLE |
| b.eq 2f |
| cmp wip, #70 // if (wip == 'F') goto FOUND_FLOAT |
| b.eq 3f |
| add \arg_offset, \arg_offset, #4 |
| // Handle extra argument in arg array taken by a long. |
| cmp wip, #74 // if (wip != 'J') goto LOOP |
| b.ne 1b |
| add \arg_offset, \arg_offset, #4 |
| b 1b // goto LOOP |
| 2: // FOUND_DOUBLE |
| add ip, \stack_ptr, \arg_offset |
| ldr ip, [ip, #OFFSET_TO_FIRST_ARGUMENT_IN_STACK] |
| str ip, [\regs, \arg_offset] |
| add \arg_offset, \arg_offset, #8 |
| b 1b |
| 3: // FOUND_FLOAT |
| add ip, \stack_ptr, \arg_offset |
| ldr wip, [ip, #OFFSET_TO_FIRST_ARGUMENT_IN_STACK] |
| str wip, [\regs, \arg_offset] |
| add \arg_offset, \arg_offset, #4 |
| b 1b |
| .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. |
| // Uses ip and ip2 as temporary. |
| .macro LOOP_OVER_INTs shorty, arg_offset, regs, refs, stack_ptr, finished |
| 1: // LOOP |
| ldrb wip, [\shorty], #1 // Load next character in shorty, and increment. |
| cbz wip, \finished // if (wip == '\0') goto finished |
| cmp wip, #74 // if (wip == 'J') goto FOUND_LONG |
| b.eq 2f |
| cmp wip, #70 // if (wip == 'F') goto SKIP_FLOAT |
| b.eq 3f |
| cmp wip, #68 // if (wip == 'D') goto SKIP_DOUBLE |
| b.eq 4f |
| add ip2, \stack_ptr, \arg_offset |
| ldr wip2, [ip2, #OFFSET_TO_FIRST_ARGUMENT_IN_STACK] |
| str wip2, [\regs, \arg_offset] |
| cmp wip, #76 // if (wip != 'L') goto loop |
| b.ne 3f |
| str wip2, [\refs, \arg_offset] |
| add \arg_offset, \arg_offset, #4 |
| b 1b |
| 2: // FOUND_LONG |
| add ip, \stack_ptr, \arg_offset |
| ldr ip, [ip, #OFFSET_TO_FIRST_ARGUMENT_IN_STACK] |
| str ip, [\regs, \arg_offset] |
| add \arg_offset, \arg_offset, #8 |
| 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 |
| |
| %def entry(): |
| /* |
| * ArtMethod entry point. |
| * |
| * On entry: |
| * x0 ArtMethod* callee |
| * rest method parameters |
| */ |
| |
| OAT_ENTRY ExecuteNterpImpl, EndExecuteNterpImpl |
| .cfi_startproc |
| sub x16, sp, #STACK_OVERFLOW_RESERVED_BYTES |
| ldr wzr, [x16] |
| /* Spill callee save regs */ |
| SPILL_ALL_CALLEE_SAVES |
| |
| // TODO: Get shorty in a better way and remove below |
| SPILL_ALL_ARGUMENTS |
| |
| // Save method in callee-save xINST |
| mov xINST, x0 |
| bl NterpGetShorty |
| // Save shorty in callee-save xIBASE. |
| mov xIBASE, x0 |
| mov x0, xINST |
| bl NterpGetCodeItem |
| mov xPC, x0 |
| |
| RESTORE_ALL_ARGUMENTS |
| |
| // Setup the stack for executing the method. |
| SETUP_STACK_FRAME xPC, xREFS, xFP, CFI_REFS |
| |
| // Setup the parameters |
| ldrh wip2, [xPC, #CODE_ITEM_INS_SIZE_OFFSET] |
| cbz wip2, .Lxmm_setup_finished |
| |
| sub ip2, ip, ip2 |
| lsl x8, ip2, #2 // x8 is now the offset for inputs into the registers array. |
| |
| // Setup shorty, pointer to inputs in FP and pointer to inputs in REFS |
| add x9, xIBASE, #1 // shorty + 1 ; ie skip return arg character |
| add x10, xFP, x8 |
| add x11, xREFS, x8 |
| |
| ldr wip, [x0, #ART_METHOD_ACCESS_FLAGS_OFFSET] |
| // TODO: could be TBNZ but we'd need a constant for log2(ART_METHOD_IS_STATIC_FLAG). |
| tst wip, #ART_METHOD_IS_STATIC_FLAG |
| b.ne .Lhandle_static_method |
| str w1, [x10], #4 |
| str w1, [x11], #4 |
| add x13, x13, #4 |
| mov x12, #0 |
| b .Lcontinue_setup_gprs |
| .Lhandle_static_method: |
| mov x12, #0 |
| LOOP_OVER_SHORTY_STORING_GPRS x1, w1, x9, x12, x10, x11, .Lgpr_setup_finished |
| .Lcontinue_setup_gprs: |
| LOOP_OVER_SHORTY_STORING_GPRS x2, w2, x9, x12, x10, x11, .Lgpr_setup_finished |
| LOOP_OVER_SHORTY_STORING_GPRS x3, w3, x9, x12, x10, x11, .Lgpr_setup_finished |
| LOOP_OVER_SHORTY_STORING_GPRS x4, w4, x9, x12, x10, x11, .Lgpr_setup_finished |
| LOOP_OVER_SHORTY_STORING_GPRS x5, w5, x9, x12, x10, x11, .Lgpr_setup_finished |
| LOOP_OVER_SHORTY_STORING_GPRS x6, w6, x9, x12, x10, x11, .Lgpr_setup_finished |
| LOOP_OVER_SHORTY_STORING_GPRS x7, w7, x9, x12, x10, x11, .Lgpr_setup_finished |
| LOOP_OVER_INTs x9, x12, x10, x11, x13, .Lgpr_setup_finished |
| .Lgpr_setup_finished: |
| add x9, xIBASE, #1 // shorty + 1 ; ie skip return arg character |
| mov x12, #0 // reset counter |
| LOOP_OVER_SHORTY_STORING_FPS d0, s0, x9, x12, x10, .Lxmm_setup_finished |
| LOOP_OVER_SHORTY_STORING_FPS d1, s1, x9, x12, x10, .Lxmm_setup_finished |
| LOOP_OVER_SHORTY_STORING_FPS d2, s2, x9, x12, x10, .Lxmm_setup_finished |
| LOOP_OVER_SHORTY_STORING_FPS d3, s3, x9, x12, x10, .Lxmm_setup_finished |
| LOOP_OVER_SHORTY_STORING_FPS d4, s4, x9, x12, x10, .Lxmm_setup_finished |
| LOOP_OVER_SHORTY_STORING_FPS d5, s5, x9, x12, x10, .Lxmm_setup_finished |
| LOOP_OVER_SHORTY_STORING_FPS d6, s6, x9, x12, x10, .Lxmm_setup_finished |
| LOOP_OVER_SHORTY_STORING_FPS d7, s7, x9, x12, x10, .Lxmm_setup_finished |
| LOOP_OVER_FPs x9, x12, x10, x13, .Lxmm_setup_finished |
| .Lxmm_setup_finished: |
| // Set the dex pc pointer. |
| add xPC, xPC, #CODE_ITEM_INSNS_OFFSET |
| CFI_DEFINE_DEX_PC_WITH_OFFSET(CFI_TMP, CFI_DEX, 0) |
| |
| // Set rIBASE |
| adr xIBASE, artNterpAsmInstructionStart |
| /* start executing the instruction at xPC */ |
| 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 |
| |
| // Note: mterp also uses the common_* names below for helpers, but that's OK |
| // as the C compiler compiled each interpreter separately. |
| common_errDivideByZero: |
| EXPORT_PC |
| bl art_quick_throw_div_zero |
| |
| // Expect array in w0, index in w1, length in w2 |
| common_errArrayIndex: |
| EXPORT_PC |
| mov x0, x1 |
| mov x1, x3 |
| 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" |
| |
| NterpNewInstance: |
| EXPORT_PC |
| // Fast-path which gets the class from thread-local cache. |
| FETCH_FROM_THREAD_CACHE x0, 2f |
| cbnz wMR, 3f |
| 4: |
| ldr lr, [xSELF, #THREAD_ALLOC_OBJECT_ENTRYPOINT_OFFSET] |
| blr lr |
| 1: |
| lsr w1, wINST, #8 // w1 <- A |
| SET_VREG_OBJECT w0, w1 // fp[A] <- value |
| FETCH_ADVANCE_INST 2 |
| GET_INST_OPCODE ip |
| GOTO_OPCODE ip |
| 2: |
| mov x0, xSELF |
| ldr x1, [sp] |
| mov x2, xPC |
| bl nterp_get_class_or_allocate_object |
| b 1b |
| 3: |
| bl art_quick_read_barrier_mark_reg00 |
| b 4b |
| |
| NterpNewArray: |
| /* new-array vA, vB, class@CCCC */ |
| EXPORT_PC |
| // Fast-path which gets the class from thread-local cache. |
| FETCH_FROM_THREAD_CACHE x0, 2f |
| cbnz wMR, 3f |
| 1: |
| lsr w1, wINST, #12 // w1<- B |
| GET_VREG w1, w1 // w1<- vB (array length) |
| ldr lr, [xSELF, #THREAD_ALLOC_ARRAY_ENTRYPOINT_OFFSET] |
| blr lr |
| ubfx w1, wINST, #8, #4 // w1<- A |
| SET_VREG_OBJECT w0, w1 |
| FETCH_ADVANCE_INST 2 |
| GET_INST_OPCODE ip |
| GOTO_OPCODE ip |
| 2: |
| mov x0, xSELF |
| ldr x1, [sp, 0] |
| mov x2, xPC |
| bl nterp_get_class_or_allocate_object |
| b 1b |
| 3: |
| bl art_quick_read_barrier_mark_reg00 |
| b 1b |
| |
| NterpPutObjectInstanceField: |
| // Fast-path which gets the field from thread-local cache. |
| FETCH_FROM_THREAD_CACHE x0, 3f |
| 1: |
| ubfx w1, wINST, #8, #4 // w1<- A |
| lsr w2, wINST, #12 // w2<- B |
| GET_VREG w2, w2 // vB (object we're operating on) |
| cbz w2, common_errNullObject // is object null? |
| GET_VREG w1, w1 // w1 <- v[A] |
| str w1, [x2, x0] |
| cbz w1, 2f |
| ldr x1, [xSELF, #THREAD_CARD_TABLE_OFFSET] |
| lsr w3, w2, #CARD_TABLE_CARD_SHIFT |
| strb w1, [x1, x3] |
| 2: |
| FETCH_ADVANCE_INST 2 |
| GET_INST_OPCODE ip |
| GOTO_OPCODE ip |
| 3: |
| mov x0, xSELF |
| ldr x1, [sp] |
| mov x2, xPC |
| EXPORT_PC |
| bl nterp_get_instance_field_offset |
| tbz w0, #31, 1b |
| CLEAR_INSTANCE_VOLATILE_MARKER w0 |
| ubfx w1, wINST, #8, #4 // w1<- A |
| lsr w2, wINST, #12 // w2<- B |
| GET_VREG w2, w2 // vB (object we're operating on) |
| cbz w2, common_errNullObject // is object null? |
| GET_VREG w1, w1 // w1 <- v[A] |
| add x3, x2, x0 |
| stlr w1, [x3] |
| cbz w1, 2b |
| ldr x1, [xSELF, #THREAD_CARD_TABLE_OFFSET] |
| lsr w3, w2, #CARD_TABLE_CARD_SHIFT |
| strb w1, [x1, x3] |
| b 2b |
| |
| NterpGetObjectInstanceField: |
| // Fast-path which gets the field from thread-local cache. |
| FETCH_FROM_THREAD_CACHE x0, 4f |
| 1: |
| ubfx w1, wINST, #8, #4 // w1<- A |
| lsr w2, wINST, #12 // w2<- B |
| GET_VREG w2, w2 // vB (object we're operating on) |
| cbz w2, common_errNullObject |
| ldr w0, [x2, x0] |
| cbnz wMR, 3f |
| 2: |
| SET_VREG_OBJECT w0, w1 // fp[A] <- value |
| FETCH_ADVANCE_INST 2 |
| GET_INST_OPCODE ip |
| GOTO_OPCODE ip |
| 3: |
| bl art_quick_read_barrier_mark_reg00 |
| b 2b |
| 4: |
| mov x0, xSELF |
| ldr x1, [sp] |
| mov x2, xPC |
| EXPORT_PC |
| bl nterp_get_instance_field_offset |
| tbz w0, #31, 1b |
| CLEAR_INSTANCE_VOLATILE_MARKER w0 |
| ubfx w1, wINST, #8, #4 // w1<- A |
| lsr w2, wINST, #12 // w2<- B |
| GET_VREG w2, w2 // vB (object we're operating on) |
| cbz w2, common_errNullObject |
| add x2, x2, x0 |
| ldar w0, [x2] |
| cbnz wMR, 6f |
| 5: |
| SET_VREG_OBJECT w0, w1 // fp[A] <- value |
| FETCH_ADVANCE_INST 2 |
| GET_INST_OPCODE ip |
| GOTO_OPCODE ip |
| 6: |
| bl art_quick_read_barrier_mark_reg00 |
| b 5b |
| |
| NterpPutObjectStaticField: |
| // Fast-path which gets the field from thread-local cache. |
| FETCH_FROM_THREAD_CACHE x0, 5f |
| 1: |
| ldr w1, [x0, #ART_FIELD_OFFSET_OFFSET] |
| ldr w0, [x0, #ART_FIELD_DECLARING_CLASS_OFFSET] |
| cbnz wMR, 4f |
| 2: |
| lsr w2, wINST, #8 // w2 <- A |
| GET_VREG w2, w2 |
| str w2, [x0, x1] |
| cbz w2, 3f |
| ldr x1, [xSELF, #THREAD_CARD_TABLE_OFFSET] |
| lsr w3, w0, #CARD_TABLE_CARD_SHIFT |
| strb w1, [x1, x3] |
| 3: |
| FETCH_ADVANCE_INST 2 |
| GET_INST_OPCODE ip |
| GOTO_OPCODE ip |
| 4: |
| bl art_quick_read_barrier_mark_reg00 |
| b 2b |
| 5: |
| mov x0, xSELF |
| ldr x1, [sp] |
| mov x2, xPC |
| EXPORT_PC |
| bl nterp_get_static_field |
| tbz x0, #0, 1b |
| CLEAR_STATIC_VOLATILE_MARKER x0 |
| ldr w1, [x0, #ART_FIELD_OFFSET_OFFSET] |
| ldr w0, [x0, #ART_FIELD_DECLARING_CLASS_OFFSET] |
| cbnz wMR, 7f |
| 6: |
| lsr w2, wINST, #8 // w1 <- A |
| GET_VREG w2, w2 |
| add ip, x0, x1 |
| stlr w2, [ip] |
| cbz w2, 3b |
| ldr x1, [xSELF, #THREAD_CARD_TABLE_OFFSET] |
| lsr w3, w0, #CARD_TABLE_CARD_SHIFT |
| strb w1, [x1, x3] |
| b 3b |
| 7: |
| bl art_quick_read_barrier_mark_reg00 |
| b 6b |
| |
| NterpGetObjectStaticField: |
| // Fast-path which gets the field from thread-local cache. |
| FETCH_FROM_THREAD_CACHE x0, 4f |
| 1: |
| ldr w1, [x0, #ART_FIELD_OFFSET_OFFSET] |
| ldr w0, [x0, #ART_FIELD_DECLARING_CLASS_OFFSET] |
| cbnz wMR, 3f |
| ldr w0, [x0, x1] |
| // No need to check the marking register, we know it's not set here. |
| 2: |
| lsr w1, wINST, #8 // w1 <- A |
| SET_VREG_OBJECT w0, w1 // fp[A] <- value |
| FETCH_ADVANCE_INST 2 |
| GET_INST_OPCODE ip |
| GOTO_OPCODE ip |
| 3: |
| bl art_quick_read_barrier_mark_reg00 |
| ldr w0, [x0, x1] |
| // Here, we know the marking register is set. |
| bl art_quick_read_barrier_mark_reg00 |
| b 2b |
| 4: |
| mov x0, xSELF |
| ldr x1, [sp] |
| mov x2, xPC |
| EXPORT_PC |
| bl nterp_get_static_field |
| tbz x0, #0, 1b |
| CLEAR_STATIC_VOLATILE_MARKER x0 |
| ldr w1, [x0, #ART_FIELD_OFFSET_OFFSET] |
| ldr w0, [x0, #ART_FIELD_DECLARING_CLASS_OFFSET] |
| cbnz wMR, 7f |
| 5: |
| add x0, x0, x1 |
| ldar w0, [x0] |
| cbnz wMR, 8f |
| 6: |
| lsr w1, wINST, #8 // w1 <- A |
| SET_VREG_OBJECT w0, w1 // fp[A] <- value |
| FETCH_ADVANCE_INST 2 |
| GET_INST_OPCODE ip |
| GOTO_OPCODE ip |
| 7: |
| bl art_quick_read_barrier_mark_reg00 |
| b 5b |
| 8: |
| bl art_quick_read_barrier_mark_reg00 |
| b 6b |
| |
| NterpGetBooleanStaticField: |
| OP_SGET load="ldrb", volatile_load="ldarb", maybe_extend="", wide=0 |
| |
| NterpGetByteStaticField: |
| OP_SGET load="ldrsb", volatile_load="ldarb", maybe_extend="sbfx w0, w0, #0, #8", wide=0 |
| |
| NterpGetCharStaticField: |
| OP_SGET load="ldrh", volatile_load="ldarh", maybe_extend="", wide=0 |
| |
| NterpGetShortStaticField: |
| OP_SGET load="ldrsh", volatile_load="ldarh", maybe_extend="sbfx w0, w0, #0, #16", wide=0 |
| |
| NterpGetWideStaticField: |
| OP_SGET load="ldr", volatile_load="ldar", maybe_extend="", wide=1 |
| |
| NterpGetIntStaticField: |
| OP_SGET load="ldr", volatile_load="ldar", maybe_extend="", wide=0 |
| |
| NterpPutStaticField: |
| OP_SPUT store="str", volatile_store="stlr", wide=0 |
| |
| NterpPutBooleanStaticField: |
| NterpPutByteStaticField: |
| OP_SPUT store="strb", volatile_store="stlrb", wide=0 |
| |
| NterpPutCharStaticField: |
| NterpPutShortStaticField: |
| OP_SPUT store="strh", volatile_store="stlrh", wide=0 |
| |
| NterpPutWideStaticField: |
| OP_SPUT store="str", volatile_store="stlr", wide=1 |
| |
| NterpPutInstanceField: |
| OP_IPUT store="str", volatile_store="stlr", wide=0 |
| |
| NterpPutBooleanInstanceField: |
| NterpPutByteInstanceField: |
| OP_IPUT store="strb", volatile_store="stlrb", wide=0 |
| |
| NterpPutCharInstanceField: |
| NterpPutShortInstanceField: |
| OP_IPUT store="strh", volatile_store="stlrh", wide=0 |
| |
| NterpPutWideInstanceField: |
| OP_IPUT store="str", volatile_store="stlr", wide=1 |
| |
| NterpGetBooleanInstanceField: |
| OP_IGET load="ldrb", volatile_load="ldarb", maybe_extend="", wide=0 |
| |
| NterpGetByteInstanceField: |
| OP_IGET load="ldrsb", volatile_load="ldarb", maybe_extend="sbfx w0, w0, #0, #8", wide=0 |
| |
| NterpGetCharInstanceField: |
| OP_IGET load="ldrh", volatile_load="ldarh", maybe_extend="", wide=0 |
| |
| NterpGetShortInstanceField: |
| OP_IGET load="ldrsh", volatile_load="ldarh", maybe_extend="sbfx w0, w0, #0, #16", wide=0 |
| |
| NterpGetWideInstanceField: |
| OP_IGET load="ldr", volatile_load="ldar", maybe_extend="", wide=1 |
| |
| NterpGetInstanceField: |
| OP_IGET load="ldr", volatile_load="ldar", maybe_extend="", wide=0 |
| |
| NterpInstanceOf: |
| /* instance-of vA, vB, class@CCCC */ |
| // Fast-path which gets the class from thread-local cache. |
| EXPORT_PC |
| FETCH_FROM_THREAD_CACHE x1, 3f |
| cbnz wMR, 4f |
| 1: |
| lsr w2, wINST, #12 // w2<- B |
| GET_VREG w0, w2 // w0<- vB (object) |
| cbz w0, 2f |
| bl artInstanceOfFromCode |
| 2: |
| ubfx w1, wINST, #8, #4 // w1<- A |
| SET_VREG w0, w1 |
| FETCH_ADVANCE_INST 2 |
| GET_INST_OPCODE ip |
| GOTO_OPCODE ip |
| 3: |
| mov x0, xSELF |
| ldr x1, [sp] |
| mov x2, xPC |
| bl nterp_get_class_or_allocate_object |
| mov x1, x0 |
| b 1b |
| 4: |
| bl art_quick_read_barrier_mark_reg01 |
| b 1b |
| |
| NterpCheckCast: |
| // Fast-path which gets the class from thread-local cache. |
| EXPORT_PC |
| FETCH_FROM_THREAD_CACHE x1, 3f |
| cbnz wMR, 4f |
| 1: |
| lsr w2, wINST, #8 // w2<- A |
| GET_VREG w0, w2 // w2<- vA (object) |
| cbz w0, 2f |
| bl art_quick_check_instance_of |
| 2: |
| FETCH_ADVANCE_INST 2 |
| GET_INST_OPCODE ip |
| GOTO_OPCODE ip |
| 3: |
| mov x0, xSELF |
| ldr x1, [sp] |
| mov x2, xPC |
| bl nterp_get_class_or_allocate_object |
| mov x1, x0 |
| b 1b |
| 4: |
| bl art_quick_read_barrier_mark_reg01 |
| b 1b |
| |
| NterpHandleHotnessOverflow: |
| add x1, xPC, xINST, lsl #1 |
| mov x2, xFP |
| bl nterp_hot_method |
| cbnz x0, 1f |
| add w2, wINST, wINST // w2<- byte offset |
| FETCH_ADVANCE_INST_RB w2 // update xPC, load wINST |
| GET_INST_OPCODE ip // extract opcode from wINST |
| GOTO_OPCODE ip // jump to next instruction |
| 1: |
| // Drop the current frame. |
| ldr ip, [xREFS, #-8] |
| mov sp, ip |
| .cfi_def_cfa sp, CALLEE_SAVES_SIZE |
| |
| // The transition frame of type SaveAllCalleeSaves saves x19 and x20, |
| // 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 |
| INCREASE_FRAME ((CALLEE_SAVES_SIZE) - 16) |
| |
| // FP callee-saves |
| stp d8, d9, [sp, #0] |
| stp d10, d11, [sp, #16] |
| stp d12, d13, [sp, #32] |
| stp d14, d15, [sp, #48] |
| |
| // GP callee-saves. |
| SAVE_TWO_REGS x21, x22, 64 |
| SAVE_TWO_REGS x23, x24, 80 |
| SAVE_TWO_REGS x25, x26, 96 |
| SAVE_TWO_REGS x27, x28, 112 |
| SAVE_TWO_REGS x29, lr, 128 |
| |
| // Setup the new frame |
| ldr x1, [x0, #OSR_DATA_FRAME_SIZE] |
| // Given stack size contains all callee saved registers, remove them. |
| sub x1, x1, #(CALLEE_SAVES_SIZE - 16) |
| |
| // We know x1 cannot be 0, as it at least contains the ArtMethod. |
| |
| // Remember CFA in a callee-save register. |
| mov xINST, sp |
| .cfi_def_cfa_register xINST |
| |
| sub sp, sp, x1 |
| |
| add x2, x0, #OSR_DATA_MEMORY |
| 2: |
| sub x1, x1, #8 |
| ldr ip, [x2, x1] |
| str ip, [sp, x1] |
| cbnz x1, 2b |
| |
| // Fetch the native PC to jump to and save it in a callee-save register. |
| ldr xFP, [x0, #OSR_DATA_NATIVE_PC] |
| |
| // Free the memory holding OSR Data. |
| bl free |
| |
| // Jump to the compiled code. |
| br xFP |
| |
| NterpHandleInvokeInterfaceOnObjectMethodRange: |
| // First argument is the 'this' pointer. |
| FETCH w1, 2 |
| GET_VREG w1, w1 |
| // Note: x1 is null, this will be handled by our SIGSEGV handler. |
| ldr w2, [x1, #MIRROR_OBJECT_CLASS_OFFSET] |
| add w2, w2, #MIRROR_CLASS_VTABLE_OFFSET_64 |
| ldr x0, [x2, w0, sxtw #3] |
| b NterpCommonInvokeInstanceRange |
| |
| NterpHandleInvokeInterfaceOnObjectMethod: |
| // First argument is the 'this' pointer. |
| FETCH w1, 2 |
| and w1, w1, #0xf |
| GET_VREG w1, w1 |
| // Note: x1 is null, this will be handled by our SIGSEGV handler. |
| ldr w2, [x1, #MIRROR_OBJECT_CLASS_OFFSET] |
| add w2, w2, #MIRROR_CLASS_VTABLE_OFFSET_64 |
| ldr x0, [x2, w0, sxtw #3] |
| b NterpCommonInvokeInstance |
| |
| // 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 |
| .cfi_endproc |
| |
| nterp_to_nterp_instance_range: |
| .cfi_startproc |
| SETUP_STACK_FOR_INVOKE |
| SETUP_RANGE_ARGUMENTS_AND_EXECUTE is_static=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 |
| |
| // This is the end of PCs contained by the OatQuickMethodHeader created for the interpreter |
| // entry point. |
| .type EndExecuteNterpImpl, #function |
| .hidden EndExecuteNterpImpl |
| .global EndExecuteNterpImpl |
| EndExecuteNterpImpl: |
| |
| // 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, #function |
| .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, #function |
| .hidden artNterpAsmInstructionStart |
| .global artNterpAsmInstructionStart |
| artNterpAsmInstructionStart = .L_op_nop |
| .text |
| |
| %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} |