| /* Subroutines used for code generation of Andes NDS32 cpu for GNU compiler |
| Copyright (C) 2012-2018 Free Software Foundation, Inc. |
| Contributed by Andes Technology Corporation. |
| |
| This file is part of GCC. |
| |
| GCC is free software; you can redistribute it and/or modify it |
| under the terms of the GNU General Public License as published |
| by the Free Software Foundation; either version 3, or (at your |
| option) any later version. |
| |
| GCC is distributed in the hope that it will be useful, but WITHOUT |
| ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
| or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public |
| License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GCC; see the file COPYING3. If not see |
| <http://www.gnu.org/licenses/>. */ |
| |
| /* ------------------------------------------------------------------------ */ |
| |
| #define IN_TARGET_CODE 1 |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "backend.h" |
| #include "target.h" |
| #include "rtl.h" |
| #include "tree.h" |
| #include "tree-pass.h" |
| #include "stringpool.h" |
| #include "attribs.h" |
| #include "df.h" |
| #include "memmodel.h" |
| #include "tm_p.h" |
| #include "optabs.h" /* For GEN_FCN. */ |
| #include "regs.h" |
| #include "emit-rtl.h" |
| #include "recog.h" |
| #include "diagnostic-core.h" |
| #include "stor-layout.h" |
| #include "varasm.h" |
| #include "calls.h" |
| #include "output.h" |
| #include "explow.h" |
| #include "expr.h" |
| #include "tm-constrs.h" |
| #include "builtins.h" |
| #include "cpplib.h" |
| #include "context.h" |
| |
| /* This file should be included last. */ |
| #include "target-def.h" |
| |
| /* ------------------------------------------------------------------------ */ |
| |
| /* This file is divided into five parts: |
| |
| PART 1: Auxiliary static variable definitions and |
| target hook static variable definitions. |
| |
| PART 2: Auxiliary static function definitions. |
| |
| PART 3: Implement target hook stuff definitions. |
| |
| PART 4: Implemet extern function definitions, |
| the prototype is in nds32-protos.h. |
| |
| PART 5: Initialize target hook structure and definitions. */ |
| |
| /* ------------------------------------------------------------------------ */ |
| |
| /* PART 1: Auxiliary static variable definitions and |
| target hook static variable definitions. */ |
| |
| /* Define intrinsic register names. |
| Please refer to nds32_intrinsic.h file, the index is corresponding to |
| 'enum nds32_intrinsic_registers' data type values. |
| NOTE that the base value starting from 1024. */ |
| static const char * const nds32_intrinsic_register_names[] = |
| { |
| "$CPU_VER", |
| "$ICM_CFG", |
| "$DCM_CFG", |
| "$MMU_CFG", |
| "$MSC_CFG", |
| "$MSC_CFG2", |
| "$CORE_ID", |
| "$FUCOP_EXIST", |
| |
| "$PSW", |
| "$IPSW", |
| "$P_IPSW", |
| "$IVB", |
| "$EVA", |
| "$P_EVA", |
| "$ITYPE", |
| "$P_ITYPE", |
| |
| "$MERR", |
| "$IPC", |
| "$P_IPC", |
| "$OIPC", |
| "$P_P0", |
| "$P_P1", |
| |
| "$INT_MASK", |
| "$INT_MASK2", |
| "$INT_MASK3", |
| "$INT_PEND", |
| "$INT_PEND2", |
| "$INT_PEND3", |
| "$SP_USR", |
| "$SP_PRIV", |
| "$INT_PRI", |
| "$INT_PRI2", |
| "$INT_PRI3", |
| "$INT_PRI4", |
| "$INT_CTRL", |
| "$INT_TRIGGER", |
| "$INT_TRIGGER2", |
| "$INT_GPR_PUSH_DIS", |
| |
| "$MMU_CTL", |
| "$L1_PPTB", |
| "$TLB_VPN", |
| "$TLB_DATA", |
| "$TLB_MISC", |
| "$VLPT_IDX", |
| "$ILMB", |
| "$DLMB", |
| |
| "$CACHE_CTL", |
| "$HSMP_SADDR", |
| "$HSMP_EADDR", |
| "$SDZ_CTL", |
| "$N12MISC_CTL", |
| "$MISC_CTL", |
| "$ECC_MISC", |
| |
| "$BPC0", |
| "$BPC1", |
| "$BPC2", |
| "$BPC3", |
| "$BPC4", |
| "$BPC5", |
| "$BPC6", |
| "$BPC7", |
| |
| "$BPA0", |
| "$BPA1", |
| "$BPA2", |
| "$BPA3", |
| "$BPA4", |
| "$BPA5", |
| "$BPA6", |
| "$BPA7", |
| |
| "$BPAM0", |
| "$BPAM1", |
| "$BPAM2", |
| "$BPAM3", |
| "$BPAM4", |
| "$BPAM5", |
| "$BPAM6", |
| "$BPAM7", |
| |
| "$BPV0", |
| "$BPV1", |
| "$BPV2", |
| "$BPV3", |
| "$BPV4", |
| "$BPV5", |
| "$BPV6", |
| "$BPV7", |
| |
| "$BPCID0", |
| "$BPCID1", |
| "$BPCID2", |
| "$BPCID3", |
| "$BPCID4", |
| "$BPCID5", |
| "$BPCID6", |
| "$BPCID7", |
| |
| "$EDM_CFG", |
| "$EDMSW", |
| "$EDM_CTL", |
| "$EDM_DTR", |
| "$BPMTC", |
| "$DIMBR", |
| |
| "$TECR0", |
| "$TECR1", |
| "$PFMC0", |
| "$PFMC1", |
| "$PFMC2", |
| "$PFM_CTL", |
| "$PFT_CTL", |
| "$HSP_CTL", |
| "$SP_BOUND", |
| "$SP_BOUND_PRIV", |
| "$SP_BASE", |
| "$SP_BASE_PRIV", |
| "$FUCOP_CTL", |
| "$PRUSR_ACC_CTL", |
| |
| "$DMA_CFG", |
| "$DMA_GCSW", |
| "$DMA_CHNSEL", |
| "$DMA_ACT", |
| "$DMA_SETUP", |
| "$DMA_ISADDR", |
| "$DMA_ESADDR", |
| "$DMA_TCNT", |
| "$DMA_STATUS", |
| "$DMA_2DSET", |
| "$DMA_2DSCTL", |
| "$DMA_RCNT", |
| "$DMA_HSTATUS", |
| |
| "$PC", |
| "$SP_USR1", |
| "$SP_USR2", |
| "$SP_USR3", |
| "$SP_PRIV1", |
| "$SP_PRIV2", |
| "$SP_PRIV3", |
| "$BG_REGION", |
| "$SFCR", |
| "$SIGN", |
| "$ISIGN", |
| "$P_ISIGN", |
| "$IFC_LP", |
| "$ITB" |
| }; |
| |
| /* Define instrinsic cctl names. */ |
| static const char * const nds32_cctl_names[] = |
| { |
| "L1D_VA_FILLCK", |
| "L1D_VA_ULCK", |
| "L1I_VA_FILLCK", |
| "L1I_VA_ULCK", |
| |
| "L1D_IX_WBINVAL", |
| "L1D_IX_INVAL", |
| "L1D_IX_WB", |
| "L1I_IX_INVAL", |
| |
| "L1D_VA_INVAL", |
| "L1D_VA_WB", |
| "L1D_VA_WBINVAL", |
| "L1I_VA_INVAL", |
| |
| "L1D_IX_RTAG", |
| "L1D_IX_RWD", |
| "L1I_IX_RTAG", |
| "L1I_IX_RWD", |
| |
| "L1D_IX_WTAG", |
| "L1D_IX_WWD", |
| "L1I_IX_WTAG", |
| "L1I_IX_WWD" |
| }; |
| |
| static const char * const nds32_dpref_names[] = |
| { |
| "SRD", |
| "MRD", |
| "SWR", |
| "MWR", |
| "PTE", |
| "CLWR" |
| }; |
| |
| /* Defining register allocation order for performance. |
| We want to allocate callee-saved registers after others. |
| It may be used by nds32_adjust_reg_alloc_order(). */ |
| static const int nds32_reg_alloc_order_for_speed[] = |
| { |
| 0, 1, 2, 3, 4, 5, 16, 17, |
| 18, 19, 20, 21, 22, 23, 24, 25, |
| 26, 27, 6, 7, 8, 9, 10, 11, |
| 12, 13, 14, 15 |
| }; |
| |
| /* Defining target-specific uses of __attribute__. */ |
| static const struct attribute_spec nds32_attribute_table[] = |
| { |
| /* Syntax: { name, min_len, max_len, decl_required, type_required, |
| function_type_required, affects_type_identity, handler, |
| exclude } */ |
| |
| /* The interrupt vid: [0-63]+ (actual vector number starts from 9 to 72). */ |
| { "interrupt", 1, 64, false, false, false, false, NULL, NULL }, |
| /* The exception vid: [1-8]+ (actual vector number starts from 1 to 8). */ |
| { "exception", 1, 8, false, false, false, false, NULL, NULL }, |
| /* Argument is user's interrupt numbers. The vector number is always 0. */ |
| { "reset", 1, 1, false, false, false, false, NULL, NULL }, |
| |
| /* The attributes describing isr nested type. */ |
| { "nested", 0, 0, false, false, false, false, NULL, NULL }, |
| { "not_nested", 0, 0, false, false, false, false, NULL, NULL }, |
| { "nested_ready", 0, 0, false, false, false, false, NULL, NULL }, |
| |
| /* The attributes describing isr register save scheme. */ |
| { "save_all", 0, 0, false, false, false, false, NULL, NULL }, |
| { "partial_save", 0, 0, false, false, false, false, NULL, NULL }, |
| |
| /* The attributes used by reset attribute. */ |
| { "nmi", 1, 1, false, false, false, false, NULL, NULL }, |
| { "warm", 1, 1, false, false, false, false, NULL, NULL }, |
| |
| /* The attribute telling no prologue/epilogue. */ |
| { "naked", 0, 0, false, false, false, false, NULL, NULL }, |
| |
| /* The last attribute spec is set to be NULL. */ |
| { NULL, 0, 0, false, false, false, false, NULL, NULL } |
| }; |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| |
| /* PART 2: Auxiliary static function definitions. */ |
| |
| /* Function to save and restore machine-specific function data. */ |
| static struct machine_function * |
| nds32_init_machine_status (void) |
| { |
| struct machine_function *machine; |
| machine = ggc_cleared_alloc<machine_function> (); |
| |
| /* Initially assume this function does not use __builtin_eh_return. */ |
| machine->use_eh_return_p = 0; |
| |
| /* Initially assume this function needs prologue/epilogue. */ |
| machine->naked_p = 0; |
| |
| /* Initially assume this function does NOT use fp_as_gp optimization. */ |
| machine->fp_as_gp_p = 0; |
| |
| /* Initially this function is not under strictly aligned situation. */ |
| machine->strict_aligned_p = 0; |
| |
| return machine; |
| } |
| |
| /* Function to compute stack frame size and |
| store into cfun->machine structure. */ |
| static void |
| nds32_compute_stack_frame (void) |
| { |
| int r; |
| int block_size; |
| bool v3pushpop_p; |
| |
| /* Because nds32_compute_stack_frame() will be called from different place, |
| everytime we enter this function, we have to assume this function |
| needs prologue/epilogue. */ |
| cfun->machine->naked_p = 0; |
| |
| |
| /* If __builtin_eh_return is used, we better have frame pointer needed |
| so that we can easily locate the stack slot of return address. */ |
| if (crtl->calls_eh_return) |
| { |
| frame_pointer_needed = 1; |
| |
| /* We need to mark eh data registers that need to be saved |
| in the stack. */ |
| cfun->machine->eh_return_data_first_regno = EH_RETURN_DATA_REGNO (0); |
| for (r = 0; EH_RETURN_DATA_REGNO (r) != INVALID_REGNUM; r++) |
| cfun->machine->eh_return_data_last_regno = r; |
| |
| cfun->machine->eh_return_data_regs_size |
| = 4 * (cfun->machine->eh_return_data_last_regno |
| - cfun->machine->eh_return_data_first_regno |
| + 1); |
| cfun->machine->use_eh_return_p = 1; |
| } |
| else |
| { |
| /* Assigning SP_REGNUM to eh_first_regno and eh_last_regno means we |
| do not need to handle __builtin_eh_return case in this function. */ |
| cfun->machine->eh_return_data_first_regno = SP_REGNUM; |
| cfun->machine->eh_return_data_last_regno = SP_REGNUM; |
| |
| cfun->machine->eh_return_data_regs_size = 0; |
| cfun->machine->use_eh_return_p = 0; |
| } |
| |
| /* Get variadic arguments size to prepare pretend arguments and |
| we will push them into stack at prologue by ourself. */ |
| cfun->machine->va_args_size = crtl->args.pretend_args_size; |
| if (cfun->machine->va_args_size != 0) |
| { |
| cfun->machine->va_args_first_regno |
| = NDS32_GPR_ARG_FIRST_REGNUM |
| + NDS32_MAX_GPR_REGS_FOR_ARGS |
| - (crtl->args.pretend_args_size / UNITS_PER_WORD); |
| cfun->machine->va_args_last_regno |
| = NDS32_GPR_ARG_FIRST_REGNUM + NDS32_MAX_GPR_REGS_FOR_ARGS - 1; |
| } |
| else |
| { |
| cfun->machine->va_args_first_regno = SP_REGNUM; |
| cfun->machine->va_args_last_regno = SP_REGNUM; |
| } |
| |
| /* Important: We need to make sure that varargs area is 8-byte alignment. */ |
| block_size = cfun->machine->va_args_size; |
| if (!NDS32_DOUBLE_WORD_ALIGN_P (block_size)) |
| { |
| cfun->machine->va_args_area_padding_bytes |
| = NDS32_ROUND_UP_DOUBLE_WORD (block_size) - block_size; |
| } |
| |
| /* Get local variables, incoming variables, and temporary variables size. |
| Note that we need to make sure it is 8-byte alignment because |
| there may be no padding bytes if we are using LRA. */ |
| cfun->machine->local_size = NDS32_ROUND_UP_DOUBLE_WORD (get_frame_size ()); |
| |
| /* Get outgoing arguments size. */ |
| cfun->machine->out_args_size = crtl->outgoing_args_size; |
| |
| /* If $fp value is required to be saved on stack, it needs 4 bytes space. |
| Check whether $fp is ever live. */ |
| cfun->machine->fp_size = (df_regs_ever_live_p (FP_REGNUM)) ? 4 : 0; |
| |
| /* If $gp value is required to be saved on stack, it needs 4 bytes space. |
| Check whether we are using PIC code genration. */ |
| cfun->machine->gp_size = (flag_pic) ? 4 : 0; |
| |
| /* If $lp value is required to be saved on stack, it needs 4 bytes space. |
| Check whether $lp is ever live. */ |
| cfun->machine->lp_size |
| = (flag_always_save_lp || df_regs_ever_live_p (LP_REGNUM)) ? 4 : 0; |
| |
| /* Initially there is no padding bytes. */ |
| cfun->machine->callee_saved_area_gpr_padding_bytes = 0; |
| |
| /* Calculate the bytes of saving callee-saved registers on stack. */ |
| cfun->machine->callee_saved_gpr_regs_size = 0; |
| cfun->machine->callee_saved_first_gpr_regno = SP_REGNUM; |
| cfun->machine->callee_saved_last_gpr_regno = SP_REGNUM; |
| cfun->machine->callee_saved_fpr_regs_size = 0; |
| cfun->machine->callee_saved_first_fpr_regno = SP_REGNUM; |
| cfun->machine->callee_saved_last_fpr_regno = SP_REGNUM; |
| |
| /* Currently, there is no need to check $r28~$r31 |
| because we will save them in another way. */ |
| for (r = 0; r < 28; r++) |
| { |
| if (NDS32_REQUIRED_CALLEE_SAVED_P (r)) |
| { |
| /* Mark the first required callee-saved register |
| (only need to set it once). |
| If first regno == SP_REGNUM, we can tell that |
| it is the first time to be here. */ |
| if (cfun->machine->callee_saved_first_gpr_regno == SP_REGNUM) |
| cfun->machine->callee_saved_first_gpr_regno = r; |
| /* Mark the last required callee-saved register. */ |
| cfun->machine->callee_saved_last_gpr_regno = r; |
| } |
| } |
| |
| /* Recording fpu callee-saved register. */ |
| if (TARGET_HARD_FLOAT) |
| { |
| for (r = NDS32_FIRST_FPR_REGNUM; r < NDS32_LAST_FPR_REGNUM; r++) |
| { |
| if (NDS32_REQUIRED_CALLEE_SAVED_P (r)) |
| { |
| /* Mark the first required callee-saved register. */ |
| if (cfun->machine->callee_saved_first_fpr_regno == SP_REGNUM) |
| { |
| /* Make first callee-saved number is even, |
| bacause we use doubleword access, and this way |
| promise 8-byte alignemt. */ |
| if (!NDS32_FPR_REGNO_OK_FOR_DOUBLE (r)) |
| cfun->machine->callee_saved_first_fpr_regno = r - 1; |
| else |
| cfun->machine->callee_saved_first_fpr_regno = r; |
| } |
| cfun->machine->callee_saved_last_fpr_regno = r; |
| } |
| } |
| |
| /* Make last callee-saved register number is odd, |
| we hope callee-saved register is even. */ |
| int last_fpr = cfun->machine->callee_saved_last_fpr_regno; |
| if (NDS32_FPR_REGNO_OK_FOR_DOUBLE (last_fpr)) |
| cfun->machine->callee_saved_last_fpr_regno++; |
| } |
| |
| /* Check if this function can omit prologue/epilogue code fragment. |
| If there is 'naked' attribute in this function, |
| we can set 'naked_p' flag to indicate that |
| we do not have to generate prologue/epilogue. |
| Or, if all the following conditions succeed, |
| we can set this function 'naked_p' as well: |
| condition 1: first_regno == last_regno == SP_REGNUM, |
| which means we do not have to save |
| any callee-saved registers. |
| condition 2: Both $lp and $fp are NOT live in this function, |
| which means we do not need to save them and there |
| is no outgoing size. |
| condition 3: There is no local_size, which means |
| we do not need to adjust $sp. */ |
| if (lookup_attribute ("naked", DECL_ATTRIBUTES (current_function_decl)) |
| || (cfun->machine->callee_saved_first_gpr_regno == SP_REGNUM |
| && cfun->machine->callee_saved_last_gpr_regno == SP_REGNUM |
| && cfun->machine->callee_saved_first_fpr_regno == SP_REGNUM |
| && cfun->machine->callee_saved_last_fpr_regno == SP_REGNUM |
| && !df_regs_ever_live_p (FP_REGNUM) |
| && !df_regs_ever_live_p (LP_REGNUM) |
| && cfun->machine->local_size == 0)) |
| { |
| /* Set this function 'naked_p' and other functions can check this flag. |
| Note that in nds32 port, the 'naked_p = 1' JUST means there is no |
| callee-saved, local size, and outgoing size. |
| The varargs space and ret instruction may still present in |
| the prologue/epilogue expanding. */ |
| cfun->machine->naked_p = 1; |
| |
| /* No need to save $fp, $gp, and $lp. |
| We should set these value to be zero |
| so that nds32_initial_elimination_offset() can work properly. */ |
| cfun->machine->fp_size = 0; |
| cfun->machine->gp_size = 0; |
| cfun->machine->lp_size = 0; |
| |
| /* If stack usage computation is required, |
| we need to provide the static stack size. */ |
| if (flag_stack_usage_info) |
| current_function_static_stack_size = 0; |
| |
| /* No need to do following adjustment, return immediately. */ |
| return; |
| } |
| |
| v3pushpop_p = NDS32_V3PUSH_AVAILABLE_P; |
| |
| /* Adjustment for v3push instructions: |
| If we are using v3push (push25/pop25) instructions, |
| we need to make sure Rb is $r6 and Re is |
| located on $r6, $r8, $r10, or $r14. |
| Some results above will be discarded and recomputed. |
| Note that it is only available under V3/V3M ISA and we |
| DO NOT setup following stuff for isr or variadic function. */ |
| if (v3pushpop_p) |
| { |
| /* Recompute: |
| cfun->machine->fp_size |
| cfun->machine->gp_size |
| cfun->machine->lp_size |
| cfun->machine->callee_saved_first_gpr_regno |
| cfun->machine->callee_saved_last_gpr_regno */ |
| |
| /* For v3push instructions, $fp, $gp, and $lp are always saved. */ |
| cfun->machine->fp_size = 4; |
| cfun->machine->gp_size = 4; |
| cfun->machine->lp_size = 4; |
| |
| /* Remember to set Rb = $r6. */ |
| cfun->machine->callee_saved_first_gpr_regno = 6; |
| |
| if (cfun->machine->callee_saved_last_gpr_regno <= 6) |
| { |
| /* Re = $r6 */ |
| cfun->machine->callee_saved_last_gpr_regno = 6; |
| } |
| else if (cfun->machine->callee_saved_last_gpr_regno <= 8) |
| { |
| /* Re = $r8 */ |
| cfun->machine->callee_saved_last_gpr_regno = 8; |
| } |
| else if (cfun->machine->callee_saved_last_gpr_regno <= 10) |
| { |
| /* Re = $r10 */ |
| cfun->machine->callee_saved_last_gpr_regno = 10; |
| } |
| else if (cfun->machine->callee_saved_last_gpr_regno <= 14) |
| { |
| /* Re = $r14 */ |
| cfun->machine->callee_saved_last_gpr_regno = 14; |
| } |
| else if (cfun->machine->callee_saved_last_gpr_regno == SP_REGNUM) |
| { |
| /* If last_regno is SP_REGNUM, which means |
| it is never changed, so set it to Re = $r6. */ |
| cfun->machine->callee_saved_last_gpr_regno = 6; |
| } |
| else |
| { |
| /* The program flow should not go here. */ |
| gcc_unreachable (); |
| } |
| } |
| |
| int sp_adjust = cfun->machine->local_size |
| + cfun->machine->out_args_size |
| + cfun->machine->callee_saved_area_gpr_padding_bytes |
| + cfun->machine->callee_saved_fpr_regs_size; |
| |
| if (!v3pushpop_p |
| && sp_adjust == 0 |
| && !frame_pointer_needed) |
| { |
| block_size = cfun->machine->fp_size |
| + cfun->machine->gp_size |
| + cfun->machine->lp_size; |
| |
| if (cfun->machine->callee_saved_last_gpr_regno != SP_REGNUM) |
| block_size += (4 * (cfun->machine->callee_saved_last_gpr_regno |
| - cfun->machine->callee_saved_first_gpr_regno |
| + 1)); |
| |
| if (!NDS32_DOUBLE_WORD_ALIGN_P (block_size)) |
| { |
| /* $r14 is last callee save register. */ |
| if (cfun->machine->callee_saved_last_gpr_regno |
| < NDS32_LAST_CALLEE_SAVE_GPR_REGNUM) |
| { |
| cfun->machine->callee_saved_last_gpr_regno++; |
| } |
| else if (cfun->machine->callee_saved_first_gpr_regno == SP_REGNUM) |
| { |
| cfun->machine->callee_saved_first_gpr_regno |
| = NDS32_FIRST_CALLEE_SAVE_GPR_REGNUM; |
| cfun->machine->callee_saved_last_gpr_regno |
| = NDS32_FIRST_CALLEE_SAVE_GPR_REGNUM; |
| } |
| } |
| } |
| |
| /* We have correctly set callee_saved_first_gpr_regno |
| and callee_saved_last_gpr_regno. |
| Initially, the callee_saved_gpr_regs_size is supposed to be 0. |
| As long as callee_saved_last_gpr_regno is not SP_REGNUM, |
| we can update callee_saved_gpr_regs_size with new size. */ |
| if (cfun->machine->callee_saved_last_gpr_regno != SP_REGNUM) |
| { |
| /* Compute pushed size of callee-saved registers. */ |
| cfun->machine->callee_saved_gpr_regs_size |
| = 4 * (cfun->machine->callee_saved_last_gpr_regno |
| - cfun->machine->callee_saved_first_gpr_regno |
| + 1); |
| } |
| |
| if (TARGET_HARD_FLOAT) |
| { |
| /* Compute size of callee svaed floating-point registers. */ |
| if (cfun->machine->callee_saved_last_fpr_regno != SP_REGNUM) |
| { |
| cfun->machine->callee_saved_fpr_regs_size |
| = 4 * (cfun->machine->callee_saved_last_fpr_regno |
| - cfun->machine->callee_saved_first_fpr_regno |
| + 1); |
| } |
| } |
| |
| /* Important: We need to make sure that |
| (fp_size + gp_size + lp_size + callee_saved_gpr_regs_size) |
| is 8-byte alignment. |
| If it is not, calculate the padding bytes. */ |
| block_size = cfun->machine->fp_size |
| + cfun->machine->gp_size |
| + cfun->machine->lp_size |
| + cfun->machine->callee_saved_gpr_regs_size; |
| if (!NDS32_DOUBLE_WORD_ALIGN_P (block_size)) |
| { |
| cfun->machine->callee_saved_area_gpr_padding_bytes |
| = NDS32_ROUND_UP_DOUBLE_WORD (block_size) - block_size; |
| } |
| |
| /* If stack usage computation is required, |
| we need to provide the static stack size. */ |
| if (flag_stack_usage_info) |
| { |
| current_function_static_stack_size |
| = NDS32_ROUND_UP_DOUBLE_WORD (block_size) |
| + cfun->machine->local_size |
| + cfun->machine->out_args_size; |
| } |
| } |
| |
| /* Function to create a parallel rtx pattern |
| which presents stack push multiple behavior. |
| The overall concept are: |
| "push registers to memory", |
| "adjust stack pointer". */ |
| static void |
| nds32_emit_stack_push_multiple (unsigned Rb, unsigned Re, |
| bool save_fp_p, bool save_gp_p, bool save_lp_p, |
| bool vaarg_p) |
| { |
| unsigned regno; |
| int extra_count; |
| int num_use_regs; |
| int par_index; |
| int offset; |
| |
| rtx reg; |
| rtx mem; |
| rtx push_rtx; |
| rtx adjust_sp_rtx; |
| rtx parallel_insn; |
| rtx dwarf; |
| |
| /* We need to provide a customized rtx which contains |
| necessary information for data analysis, |
| so we create a parallel rtx like this: |
| (parallel [(set (mem (plus (reg:SI SP_REGNUM) (const_int -32))) |
| (reg:SI Rb)) |
| (set (mem (plus (reg:SI SP_REGNUM) (const_int -28))) |
| (reg:SI Rb+1)) |
| ... |
| (set (mem (plus (reg:SI SP_REGNUM) (const_int -16))) |
| (reg:SI Re)) |
| (set (mem (plus (reg:SI SP_REGNUM) (const_int -12))) |
| (reg:SI FP_REGNUM)) |
| (set (mem (plus (reg:SI SP_REGNUM) (const_int -8))) |
| (reg:SI GP_REGNUM)) |
| (set (mem (plus (reg:SI SP_REGNUM) (const_int -4))) |
| (reg:SI LP_REGNUM)) |
| (set (reg:SI SP_REGNUM) |
| (plus (reg:SI SP_REGNUM) (const_int -32)))]) */ |
| |
| /* Calculate the number of registers that will be pushed. */ |
| extra_count = 0; |
| if (save_fp_p) |
| extra_count++; |
| if (save_gp_p) |
| extra_count++; |
| if (save_lp_p) |
| extra_count++; |
| /* Note that Rb and Re may be SP_REGNUM. DO NOT count it in. */ |
| if (Rb == SP_REGNUM && Re == SP_REGNUM) |
| num_use_regs = extra_count; |
| else |
| num_use_regs = Re - Rb + 1 + extra_count; |
| |
| /* In addition to used registers, |
| we need one more space for (set sp sp-x) rtx. */ |
| parallel_insn = gen_rtx_PARALLEL (VOIDmode, |
| rtvec_alloc (num_use_regs + 1)); |
| par_index = 0; |
| |
| /* Initialize offset and start to create push behavior. */ |
| offset = -(num_use_regs * 4); |
| |
| /* Create (set mem regX) from Rb, Rb+1 up to Re. */ |
| for (regno = Rb; regno <= Re; regno++) |
| { |
| /* Rb and Re may be SP_REGNUM. |
| We need to break this loop immediately. */ |
| if (regno == SP_REGNUM) |
| break; |
| |
| reg = gen_rtx_REG (SImode, regno); |
| mem = gen_frame_mem (SImode, plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset)); |
| push_rtx = gen_rtx_SET (mem, reg); |
| XVECEXP (parallel_insn, 0, par_index) = push_rtx; |
| RTX_FRAME_RELATED_P (push_rtx) = 1; |
| offset = offset + 4; |
| par_index++; |
| } |
| |
| /* Create (set mem fp), (set mem gp), and (set mem lp) if necessary. */ |
| if (save_fp_p) |
| { |
| reg = gen_rtx_REG (SImode, FP_REGNUM); |
| mem = gen_frame_mem (SImode, plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset)); |
| push_rtx = gen_rtx_SET (mem, reg); |
| XVECEXP (parallel_insn, 0, par_index) = push_rtx; |
| RTX_FRAME_RELATED_P (push_rtx) = 1; |
| offset = offset + 4; |
| par_index++; |
| } |
| if (save_gp_p) |
| { |
| reg = gen_rtx_REG (SImode, GP_REGNUM); |
| mem = gen_frame_mem (SImode, plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset)); |
| push_rtx = gen_rtx_SET (mem, reg); |
| XVECEXP (parallel_insn, 0, par_index) = push_rtx; |
| RTX_FRAME_RELATED_P (push_rtx) = 1; |
| offset = offset + 4; |
| par_index++; |
| } |
| if (save_lp_p) |
| { |
| reg = gen_rtx_REG (SImode, LP_REGNUM); |
| mem = gen_frame_mem (SImode, plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset)); |
| push_rtx = gen_rtx_SET (mem, reg); |
| XVECEXP (parallel_insn, 0, par_index) = push_rtx; |
| RTX_FRAME_RELATED_P (push_rtx) = 1; |
| offset = offset + 4; |
| par_index++; |
| } |
| |
| /* Create (set sp sp-x). */ |
| |
| /* We need to re-calculate the offset value again for adjustment. */ |
| offset = -(num_use_regs * 4); |
| adjust_sp_rtx |
| = gen_rtx_SET (stack_pointer_rtx, |
| plus_constant (Pmode, stack_pointer_rtx, offset)); |
| XVECEXP (parallel_insn, 0, par_index) = adjust_sp_rtx; |
| RTX_FRAME_RELATED_P (adjust_sp_rtx) = 1; |
| |
| parallel_insn = emit_insn (parallel_insn); |
| |
| /* The insn rtx 'parallel_insn' will change frame layout. |
| We need to use RTX_FRAME_RELATED_P so that GCC is able to |
| generate CFI (Call Frame Information) stuff. */ |
| RTX_FRAME_RELATED_P (parallel_insn) = 1; |
| |
| /* Don't use GCC's logic for CFI info if we are generate a push for VAARG |
| since we will not restore those register at epilogue. */ |
| if (vaarg_p) |
| { |
| dwarf = alloc_reg_note (REG_CFA_ADJUST_CFA, |
| copy_rtx (adjust_sp_rtx), NULL_RTX); |
| REG_NOTES (parallel_insn) = dwarf; |
| } |
| } |
| |
| /* Function to create a parallel rtx pattern |
| which presents stack pop multiple behavior. |
| The overall concept are: |
| "pop registers from memory", |
| "adjust stack pointer". */ |
| static void |
| nds32_emit_stack_pop_multiple (unsigned Rb, unsigned Re, |
| bool save_fp_p, bool save_gp_p, bool save_lp_p) |
| { |
| unsigned regno; |
| int extra_count; |
| int num_use_regs; |
| int par_index; |
| int offset; |
| |
| rtx reg; |
| rtx mem; |
| rtx pop_rtx; |
| rtx adjust_sp_rtx; |
| rtx parallel_insn; |
| rtx dwarf = NULL_RTX; |
| |
| /* We need to provide a customized rtx which contains |
| necessary information for data analysis, |
| so we create a parallel rtx like this: |
| (parallel [(set (reg:SI Rb) |
| (mem (reg:SI SP_REGNUM))) |
| (set (reg:SI Rb+1) |
| (mem (plus (reg:SI SP_REGNUM) (const_int 4)))) |
| ... |
| (set (reg:SI Re) |
| (mem (plus (reg:SI SP_REGNUM) (const_int 16)))) |
| (set (reg:SI FP_REGNUM) |
| (mem (plus (reg:SI SP_REGNUM) (const_int 20)))) |
| (set (reg:SI GP_REGNUM) |
| (mem (plus (reg:SI SP_REGNUM) (const_int 24)))) |
| (set (reg:SI LP_REGNUM) |
| (mem (plus (reg:SI SP_REGNUM) (const_int 28)))) |
| (set (reg:SI SP_REGNUM) |
| (plus (reg:SI SP_REGNUM) (const_int 32)))]) */ |
| |
| /* Calculate the number of registers that will be poped. */ |
| extra_count = 0; |
| if (save_fp_p) |
| extra_count++; |
| if (save_gp_p) |
| extra_count++; |
| if (save_lp_p) |
| extra_count++; |
| /* Note that Rb and Re may be SP_REGNUM. DO NOT count it in. */ |
| if (Rb == SP_REGNUM && Re == SP_REGNUM) |
| num_use_regs = extra_count; |
| else |
| num_use_regs = Re - Rb + 1 + extra_count; |
| |
| /* In addition to used registers, |
| we need one more space for (set sp sp+x) rtx. */ |
| parallel_insn = gen_rtx_PARALLEL (VOIDmode, |
| rtvec_alloc (num_use_regs + 1)); |
| par_index = 0; |
| |
| /* Initialize offset and start to create pop behavior. */ |
| offset = 0; |
| |
| /* Create (set regX mem) from Rb, Rb+1 up to Re. */ |
| for (regno = Rb; regno <= Re; regno++) |
| { |
| /* Rb and Re may be SP_REGNUM. |
| We need to break this loop immediately. */ |
| if (regno == SP_REGNUM) |
| break; |
| |
| reg = gen_rtx_REG (SImode, regno); |
| mem = gen_frame_mem (SImode, plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset)); |
| pop_rtx = gen_rtx_SET (reg, mem); |
| XVECEXP (parallel_insn, 0, par_index) = pop_rtx; |
| RTX_FRAME_RELATED_P (pop_rtx) = 1; |
| offset = offset + 4; |
| par_index++; |
| |
| dwarf = alloc_reg_note (REG_CFA_RESTORE, reg, dwarf); |
| } |
| |
| /* Create (set fp mem), (set gp mem), and (set lp mem) if necessary. */ |
| if (save_fp_p) |
| { |
| reg = gen_rtx_REG (SImode, FP_REGNUM); |
| mem = gen_frame_mem (SImode, plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset)); |
| pop_rtx = gen_rtx_SET (reg, mem); |
| XVECEXP (parallel_insn, 0, par_index) = pop_rtx; |
| RTX_FRAME_RELATED_P (pop_rtx) = 1; |
| offset = offset + 4; |
| par_index++; |
| |
| dwarf = alloc_reg_note (REG_CFA_RESTORE, reg, dwarf); |
| } |
| if (save_gp_p) |
| { |
| reg = gen_rtx_REG (SImode, GP_REGNUM); |
| mem = gen_frame_mem (SImode, plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset)); |
| pop_rtx = gen_rtx_SET (reg, mem); |
| XVECEXP (parallel_insn, 0, par_index) = pop_rtx; |
| RTX_FRAME_RELATED_P (pop_rtx) = 1; |
| offset = offset + 4; |
| par_index++; |
| |
| dwarf = alloc_reg_note (REG_CFA_RESTORE, reg, dwarf); |
| } |
| if (save_lp_p) |
| { |
| reg = gen_rtx_REG (SImode, LP_REGNUM); |
| mem = gen_frame_mem (SImode, plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset)); |
| pop_rtx = gen_rtx_SET (reg, mem); |
| XVECEXP (parallel_insn, 0, par_index) = pop_rtx; |
| RTX_FRAME_RELATED_P (pop_rtx) = 1; |
| offset = offset + 4; |
| par_index++; |
| |
| dwarf = alloc_reg_note (REG_CFA_RESTORE, reg, dwarf); |
| } |
| |
| /* Create (set sp sp+x). */ |
| |
| /* The offset value is already in place. No need to re-calculate it. */ |
| adjust_sp_rtx |
| = gen_rtx_SET (stack_pointer_rtx, |
| plus_constant (Pmode, stack_pointer_rtx, offset)); |
| XVECEXP (parallel_insn, 0, par_index) = adjust_sp_rtx; |
| |
| /* Tell gcc we adjust SP in this insn. */ |
| dwarf = alloc_reg_note (REG_CFA_ADJUST_CFA, copy_rtx (adjust_sp_rtx), dwarf); |
| |
| parallel_insn = emit_insn (parallel_insn); |
| |
| /* The insn rtx 'parallel_insn' will change frame layout. |
| We need to use RTX_FRAME_RELATED_P so that GCC is able to |
| generate CFI (Call Frame Information) stuff. */ |
| RTX_FRAME_RELATED_P (parallel_insn) = 1; |
| |
| /* Add CFI info by manual. */ |
| REG_NOTES (parallel_insn) = dwarf; |
| } |
| |
| /* Function to create a parallel rtx pattern |
| which presents stack v3push behavior. |
| The overall concept are: |
| "push registers to memory", |
| "adjust stack pointer". */ |
| static void |
| nds32_emit_stack_v3push (unsigned Rb, |
| unsigned Re, |
| unsigned imm8u) |
| { |
| unsigned regno; |
| int num_use_regs; |
| int par_index; |
| int offset; |
| |
| rtx reg; |
| rtx mem; |
| rtx push_rtx; |
| rtx adjust_sp_rtx; |
| rtx parallel_insn; |
| |
| /* We need to provide a customized rtx which contains |
| necessary information for data analysis, |
| so we create a parallel rtx like this: |
| (parallel [(set (mem (plus (reg:SI SP_REGNUM) (const_int -32))) |
| (reg:SI Rb)) |
| (set (mem (plus (reg:SI SP_REGNUM) (const_int -28))) |
| (reg:SI Rb+1)) |
| ... |
| (set (mem (plus (reg:SI SP_REGNUM) (const_int -16))) |
| (reg:SI Re)) |
| (set (mem (plus (reg:SI SP_REGNUM) (const_int -12))) |
| (reg:SI FP_REGNUM)) |
| (set (mem (plus (reg:SI SP_REGNUM) (const_int -8))) |
| (reg:SI GP_REGNUM)) |
| (set (mem (plus (reg:SI SP_REGNUM) (const_int -4))) |
| (reg:SI LP_REGNUM)) |
| (set (reg:SI SP_REGNUM) |
| (plus (reg:SI SP_REGNUM) (const_int -32-imm8u)))]) */ |
| |
| /* Calculate the number of registers that will be pushed. |
| Since $fp, $gp, and $lp is always pushed with v3push instruction, |
| we need to count these three registers. |
| Under v3push, Rb is $r6, while Re is $r6, $r8, $r10, or $r14. |
| So there is no need to worry about Rb=Re=SP_REGNUM case. */ |
| num_use_regs = Re - Rb + 1 + 3; |
| |
| /* In addition to used registers, |
| we need one more space for (set sp sp-x-imm8u) rtx. */ |
| parallel_insn = gen_rtx_PARALLEL (VOIDmode, |
| rtvec_alloc (num_use_regs + 1)); |
| par_index = 0; |
| |
| /* Initialize offset and start to create push behavior. */ |
| offset = -(num_use_regs * 4); |
| |
| /* Create (set mem regX) from Rb, Rb+1 up to Re. |
| Under v3push, Rb is $r6, while Re is $r6, $r8, $r10, or $r14. |
| So there is no need to worry about Rb=Re=SP_REGNUM case. */ |
| for (regno = Rb; regno <= Re; regno++) |
| { |
| reg = gen_rtx_REG (SImode, regno); |
| mem = gen_frame_mem (SImode, plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset)); |
| push_rtx = gen_rtx_SET (mem, reg); |
| XVECEXP (parallel_insn, 0, par_index) = push_rtx; |
| RTX_FRAME_RELATED_P (push_rtx) = 1; |
| offset = offset + 4; |
| par_index++; |
| } |
| |
| /* Create (set mem fp). */ |
| reg = gen_rtx_REG (SImode, FP_REGNUM); |
| mem = gen_frame_mem (SImode, plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset)); |
| push_rtx = gen_rtx_SET (mem, reg); |
| XVECEXP (parallel_insn, 0, par_index) = push_rtx; |
| RTX_FRAME_RELATED_P (push_rtx) = 1; |
| offset = offset + 4; |
| par_index++; |
| /* Create (set mem gp). */ |
| reg = gen_rtx_REG (SImode, GP_REGNUM); |
| mem = gen_frame_mem (SImode, plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset)); |
| push_rtx = gen_rtx_SET (mem, reg); |
| XVECEXP (parallel_insn, 0, par_index) = push_rtx; |
| RTX_FRAME_RELATED_P (push_rtx) = 1; |
| offset = offset + 4; |
| par_index++; |
| /* Create (set mem lp). */ |
| reg = gen_rtx_REG (SImode, LP_REGNUM); |
| mem = gen_frame_mem (SImode, plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset)); |
| push_rtx = gen_rtx_SET (mem, reg); |
| XVECEXP (parallel_insn, 0, par_index) = push_rtx; |
| RTX_FRAME_RELATED_P (push_rtx) = 1; |
| offset = offset + 4; |
| par_index++; |
| |
| /* Create (set sp sp-x-imm8u). */ |
| |
| /* We need to re-calculate the offset value again for adjustment. */ |
| offset = -(num_use_regs * 4); |
| adjust_sp_rtx |
| = gen_rtx_SET (stack_pointer_rtx, |
| plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset - imm8u)); |
| XVECEXP (parallel_insn, 0, par_index) = adjust_sp_rtx; |
| RTX_FRAME_RELATED_P (adjust_sp_rtx) = 1; |
| |
| parallel_insn = emit_insn (parallel_insn); |
| |
| /* The insn rtx 'parallel_insn' will change frame layout. |
| We need to use RTX_FRAME_RELATED_P so that GCC is able to |
| generate CFI (Call Frame Information) stuff. */ |
| RTX_FRAME_RELATED_P (parallel_insn) = 1; |
| } |
| |
| /* Function to create a parallel rtx pattern |
| which presents stack v3pop behavior. |
| The overall concept are: |
| "pop registers from memory", |
| "adjust stack pointer". */ |
| static void |
| nds32_emit_stack_v3pop (unsigned Rb, |
| unsigned Re, |
| unsigned imm8u) |
| { |
| unsigned regno; |
| int num_use_regs; |
| int par_index; |
| int offset; |
| |
| rtx reg; |
| rtx mem; |
| rtx pop_rtx; |
| rtx adjust_sp_rtx; |
| rtx parallel_insn; |
| rtx dwarf = NULL_RTX; |
| |
| /* We need to provide a customized rtx which contains |
| necessary information for data analysis, |
| so we create a parallel rtx like this: |
| (parallel [(set (reg:SI Rb) |
| (mem (reg:SI SP_REGNUM))) |
| (set (reg:SI Rb+1) |
| (mem (plus (reg:SI SP_REGNUM) (const_int 4)))) |
| ... |
| (set (reg:SI Re) |
| (mem (plus (reg:SI SP_REGNUM) (const_int 16)))) |
| (set (reg:SI FP_REGNUM) |
| (mem (plus (reg:SI SP_REGNUM) (const_int 20)))) |
| (set (reg:SI GP_REGNUM) |
| (mem (plus (reg:SI SP_REGNUM) (const_int 24)))) |
| (set (reg:SI LP_REGNUM) |
| (mem (plus (reg:SI SP_REGNUM) (const_int 28)))) |
| (set (reg:SI SP_REGNUM) |
| (plus (reg:SI SP_REGNUM) (const_int 32+imm8u)))]) */ |
| |
| /* Calculate the number of registers that will be poped. |
| Since $fp, $gp, and $lp is always poped with v3pop instruction, |
| we need to count these three registers. |
| Under v3push, Rb is $r6, while Re is $r6, $r8, $r10, or $r14. |
| So there is no need to worry about Rb=Re=SP_REGNUM case. */ |
| num_use_regs = Re - Rb + 1 + 3; |
| |
| /* In addition to used registers, |
| we need one more space for (set sp sp+x+imm8u) rtx. */ |
| parallel_insn = gen_rtx_PARALLEL (VOIDmode, |
| rtvec_alloc (num_use_regs + 1)); |
| par_index = 0; |
| |
| /* Initialize offset and start to create pop behavior. */ |
| offset = 0; |
| |
| /* Create (set regX mem) from Rb, Rb+1 up to Re. |
| Under v3pop, Rb is $r6, while Re is $r6, $r8, $r10, or $r14. |
| So there is no need to worry about Rb=Re=SP_REGNUM case. */ |
| for (regno = Rb; regno <= Re; regno++) |
| { |
| reg = gen_rtx_REG (SImode, regno); |
| mem = gen_frame_mem (SImode, plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset)); |
| pop_rtx = gen_rtx_SET (reg, mem); |
| XVECEXP (parallel_insn, 0, par_index) = pop_rtx; |
| RTX_FRAME_RELATED_P (pop_rtx) = 1; |
| offset = offset + 4; |
| par_index++; |
| |
| dwarf = alloc_reg_note (REG_CFA_RESTORE, reg, dwarf); |
| } |
| |
| /* Create (set fp mem). */ |
| reg = gen_rtx_REG (SImode, FP_REGNUM); |
| mem = gen_frame_mem (SImode, plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset)); |
| pop_rtx = gen_rtx_SET (reg, mem); |
| XVECEXP (parallel_insn, 0, par_index) = pop_rtx; |
| RTX_FRAME_RELATED_P (pop_rtx) = 1; |
| offset = offset + 4; |
| par_index++; |
| dwarf = alloc_reg_note (REG_CFA_RESTORE, reg, dwarf); |
| |
| /* Create (set gp mem). */ |
| reg = gen_rtx_REG (SImode, GP_REGNUM); |
| mem = gen_frame_mem (SImode, plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset)); |
| pop_rtx = gen_rtx_SET (reg, mem); |
| XVECEXP (parallel_insn, 0, par_index) = pop_rtx; |
| RTX_FRAME_RELATED_P (pop_rtx) = 1; |
| offset = offset + 4; |
| par_index++; |
| dwarf = alloc_reg_note (REG_CFA_RESTORE, reg, dwarf); |
| |
| /* Create (set lp mem ). */ |
| reg = gen_rtx_REG (SImode, LP_REGNUM); |
| mem = gen_frame_mem (SImode, plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset)); |
| pop_rtx = gen_rtx_SET (reg, mem); |
| XVECEXP (parallel_insn, 0, par_index) = pop_rtx; |
| RTX_FRAME_RELATED_P (pop_rtx) = 1; |
| offset = offset + 4; |
| par_index++; |
| dwarf = alloc_reg_note (REG_CFA_RESTORE, reg, dwarf); |
| |
| /* Create (set sp sp+x+imm8u). */ |
| |
| /* The offset value is already in place. No need to re-calculate it. */ |
| adjust_sp_rtx |
| = gen_rtx_SET (stack_pointer_rtx, |
| plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset + imm8u)); |
| XVECEXP (parallel_insn, 0, par_index) = adjust_sp_rtx; |
| |
| if (frame_pointer_needed) |
| { |
| /* (expr_list:REG_CFA_DEF_CFA (plus:SI (reg/f:SI $sp) |
| (const_int 0)) |
| mean reset frame pointer to $sp and reset to offset 0. */ |
| rtx cfa_adjust_rtx = gen_rtx_PLUS (Pmode, stack_pointer_rtx, |
| const0_rtx); |
| dwarf = alloc_reg_note (REG_CFA_DEF_CFA, cfa_adjust_rtx, dwarf); |
| } |
| else |
| { |
| /* Tell gcc we adjust SP in this insn. */ |
| dwarf = alloc_reg_note (REG_CFA_ADJUST_CFA, |
| copy_rtx (adjust_sp_rtx), dwarf); |
| } |
| |
| parallel_insn = emit_insn (parallel_insn); |
| |
| /* The insn rtx 'parallel_insn' will change frame layout. |
| We need to use RTX_FRAME_RELATED_P so that GCC is able to |
| generate CFI (Call Frame Information) stuff. */ |
| RTX_FRAME_RELATED_P (parallel_insn) = 1; |
| |
| /* Add CFI info by manual. */ |
| REG_NOTES (parallel_insn) = dwarf; |
| } |
| |
| /* Function that may creates more instructions |
| for large value on adjusting stack pointer. |
| |
| In nds32 target, 'addi' can be used for stack pointer |
| adjustment in prologue/epilogue stage. |
| However, sometimes there are too many local variables so that |
| the adjustment value is not able to be fit in the 'addi' instruction. |
| One solution is to move value into a register |
| and then use 'add' instruction. |
| In practice, we use TA_REGNUM ($r15) to accomplish this purpose. */ |
| static void |
| nds32_emit_adjust_frame (rtx to_reg, rtx from_reg, int adjust_value) |
| { |
| rtx tmp_reg; |
| rtx frame_adjust_insn; |
| rtx adjust_value_rtx = GEN_INT (adjust_value); |
| |
| if (adjust_value == 0) |
| return; |
| |
| if (!satisfies_constraint_Is15 (adjust_value_rtx)) |
| { |
| /* The value is not able to fit in single addi instruction. |
| Create more instructions of moving value into a register |
| and then add stack pointer with it. */ |
| |
| /* $r15 is going to be temporary register to hold the value. */ |
| tmp_reg = gen_rtx_REG (SImode, TA_REGNUM); |
| |
| /* Create one more instruction to move value |
| into the temporary register. */ |
| emit_move_insn (tmp_reg, adjust_value_rtx); |
| |
| /* Create new 'add' rtx. */ |
| frame_adjust_insn = gen_addsi3 (to_reg, |
| from_reg, |
| tmp_reg); |
| /* Emit rtx into insn list and receive its transformed insn rtx. */ |
| frame_adjust_insn = emit_insn (frame_adjust_insn); |
| |
| /* Because (tmp_reg <- full_value) may be split into two |
| rtl patterns, we can not set its RTX_FRAME_RELATED_P. |
| We need to construct another (sp <- sp + full_value) |
| and then insert it into sp_adjust_insn's reg note to |
| represent a frame related expression. |
| GCC knows how to refer it and output debug information. */ |
| |
| rtx plus_rtx; |
| rtx set_rtx; |
| |
| plus_rtx = plus_constant (Pmode, from_reg, adjust_value); |
| set_rtx = gen_rtx_SET (to_reg, plus_rtx); |
| add_reg_note (frame_adjust_insn, REG_FRAME_RELATED_EXPR, set_rtx); |
| } |
| else |
| { |
| /* Generate sp adjustment instruction if and only if sp_adjust != 0. */ |
| frame_adjust_insn = gen_addsi3 (to_reg, |
| from_reg, |
| adjust_value_rtx); |
| /* Emit rtx into instructions list and receive INSN rtx form. */ |
| frame_adjust_insn = emit_insn (frame_adjust_insn); |
| } |
| |
| /* The insn rtx 'sp_adjust_insn' will change frame layout. |
| We need to use RTX_FRAME_RELATED_P so that GCC is able to |
| generate CFI (Call Frame Information) stuff. */ |
| RTX_FRAME_RELATED_P (frame_adjust_insn) = 1; |
| } |
| |
| /* Return true if MODE/TYPE need double word alignment. */ |
| static bool |
| nds32_needs_double_word_align (machine_mode mode, const_tree type) |
| { |
| unsigned int align; |
| |
| /* Pick up the alignment according to the mode or type. */ |
| align = NDS32_MODE_TYPE_ALIGN (mode, type); |
| |
| return (align > PARM_BOUNDARY); |
| } |
| |
| /* Return true if FUNC is a naked function. */ |
| static bool |
| nds32_naked_function_p (tree func) |
| { |
| tree t; |
| |
| if (TREE_CODE (func) != FUNCTION_DECL) |
| abort (); |
| |
| t = lookup_attribute ("naked", DECL_ATTRIBUTES (func)); |
| |
| return (t != NULL_TREE); |
| } |
| |
| /* Function that determine whether a load postincrement is a good thing to use |
| for a given mode. */ |
| bool |
| nds32_use_load_post_increment (machine_mode mode) |
| { |
| return (GET_MODE_SIZE (mode) <= GET_MODE_SIZE(E_DImode)); |
| } |
| |
| /* Function that check if 'X' is a valid address register. |
| The variable 'STRICT' is very important to |
| make decision for register number. |
| |
| STRICT : true |
| => We are in reload pass or after reload pass. |
| The register number should be strictly limited in general registers. |
| |
| STRICT : false |
| => Before reload pass, we are free to use any register number. */ |
| static bool |
| nds32_address_register_rtx_p (rtx x, bool strict) |
| { |
| int regno; |
| |
| if (GET_CODE (x) != REG) |
| return false; |
| |
| regno = REGNO (x); |
| |
| if (strict) |
| return REGNO_OK_FOR_BASE_P (regno); |
| else |
| return true; |
| } |
| |
| /* Function that check if 'INDEX' is valid to be a index rtx for address. |
| |
| OUTER_MODE : Machine mode of outer address rtx. |
| INDEX : Check if this rtx is valid to be a index for address. |
| STRICT : If it is true, we are in reload pass or after reload pass. */ |
| static bool |
| nds32_legitimate_index_p (machine_mode outer_mode, |
| rtx index, |
| bool strict) |
| { |
| int regno; |
| rtx op0; |
| rtx op1; |
| |
| switch (GET_CODE (index)) |
| { |
| case REG: |
| regno = REGNO (index); |
| /* If we are in reload pass or after reload pass, |
| we need to limit it to general register. */ |
| if (strict) |
| return REGNO_OK_FOR_INDEX_P (regno); |
| else |
| return true; |
| |
| case CONST_INT: |
| /* The alignment of the integer value is determined by 'outer_mode'. */ |
| switch (GET_MODE_SIZE (outer_mode)) |
| { |
| case 1: |
| /* Further check if the value is legal for the 'outer_mode'. */ |
| if (satisfies_constraint_Is15 (index)) |
| return true; |
| break; |
| |
| case 2: |
| /* Further check if the value is legal for the 'outer_mode'. */ |
| if (satisfies_constraint_Is16 (index)) |
| { |
| /* If it is not under strictly aligned situation, |
| we can return true without checking alignment. */ |
| if (!cfun->machine->strict_aligned_p) |
| return true; |
| /* Make sure address is half word alignment. */ |
| else if (NDS32_HALF_WORD_ALIGN_P (INTVAL (index))) |
| return true; |
| } |
| break; |
| |
| case 4: |
| /* Further check if the value is legal for the 'outer_mode'. */ |
| if (satisfies_constraint_Is17 (index)) |
| { |
| if ((TARGET_FPU_SINGLE || TARGET_FPU_DOUBLE)) |
| { |
| if (!satisfies_constraint_Is14 (index)) |
| return false; |
| } |
| |
| /* If it is not under strictly aligned situation, |
| we can return true without checking alignment. */ |
| if (!cfun->machine->strict_aligned_p) |
| return true; |
| /* Make sure address is word alignment. */ |
| else if (NDS32_SINGLE_WORD_ALIGN_P (INTVAL (index))) |
| return true; |
| } |
| break; |
| |
| case 8: |
| if (satisfies_constraint_Is17 (gen_int_mode (INTVAL (index) + 4, |
| SImode))) |
| { |
| if ((TARGET_FPU_SINGLE || TARGET_FPU_DOUBLE)) |
| { |
| if (!satisfies_constraint_Is14 (index)) |
| return false; |
| } |
| |
| /* If it is not under strictly aligned situation, |
| we can return true without checking alignment. */ |
| if (!cfun->machine->strict_aligned_p) |
| return true; |
| /* Make sure address is word alignment. |
| Currently we do not have 64-bit load/store yet, |
| so we will use two 32-bit load/store instructions to do |
| memory access and they are single word alignment. */ |
| else if (NDS32_SINGLE_WORD_ALIGN_P (INTVAL (index))) |
| return true; |
| } |
| break; |
| |
| default: |
| return false; |
| } |
| |
| return false; |
| |
| case MULT: |
| op0 = XEXP (index, 0); |
| op1 = XEXP (index, 1); |
| |
| if (REG_P (op0) && CONST_INT_P (op1)) |
| { |
| int multiplier; |
| multiplier = INTVAL (op1); |
| |
| /* We only allow (mult reg const_int_1), (mult reg const_int_2), |
| (mult reg const_int_4) or (mult reg const_int_8). */ |
| if (multiplier != 1 && multiplier != 2 |
| && multiplier != 4 && multiplier != 8) |
| return false; |
| |
| regno = REGNO (op0); |
| /* Limit it in general registers if we are |
| in reload pass or after reload pass. */ |
| if(strict) |
| return REGNO_OK_FOR_INDEX_P (regno); |
| else |
| return true; |
| } |
| |
| return false; |
| |
| case ASHIFT: |
| op0 = XEXP (index, 0); |
| op1 = XEXP (index, 1); |
| |
| if (REG_P (op0) && CONST_INT_P (op1)) |
| { |
| int sv; |
| /* op1 is already the sv value for use to do left shift. */ |
| sv = INTVAL (op1); |
| |
| /* We only allow (ashift reg const_int_0) |
| or (ashift reg const_int_1) or (ashift reg const_int_2) or |
| (ashift reg const_int_3). */ |
| if (sv != 0 && sv != 1 && sv !=2 && sv != 3) |
| return false; |
| |
| regno = REGNO (op0); |
| /* Limit it in general registers if we are |
| in reload pass or after reload pass. */ |
| if(strict) |
| return REGNO_OK_FOR_INDEX_P (regno); |
| else |
| return true; |
| } |
| |
| return false; |
| |
| default: |
| return false; |
| } |
| } |
| |
| static void |
| nds32_register_pass ( |
| rtl_opt_pass *(*make_pass_func) (gcc::context *), |
| enum pass_positioning_ops pass_pos, |
| const char *ref_pass_name) |
| { |
| opt_pass *new_opt_pass = make_pass_func (g); |
| |
| struct register_pass_info insert_pass = |
| { |
| new_opt_pass, /* pass */ |
| ref_pass_name, /* reference_pass_name */ |
| 1, /* ref_pass_instance_number */ |
| pass_pos /* po_op */ |
| }; |
| |
| register_pass (&insert_pass); |
| } |
| |
| /* This function is called from nds32_option_override (). |
| All new passes should be registered here. */ |
| static void |
| nds32_register_passes (void) |
| { |
| nds32_register_pass ( |
| make_pass_nds32_relax_opt, |
| PASS_POS_INSERT_AFTER, |
| "mach"); |
| } |
| |
| /* ------------------------------------------------------------------------ */ |
| |
| /* PART 3: Implement target hook stuff definitions. */ |
| |
| |
| /* Computing the Length of an Insn. |
| Modifies the length assigned to instruction INSN. |
| LEN is the initially computed length of the insn. */ |
| int |
| nds32_adjust_insn_length (rtx_insn *insn, int length) |
| { |
| int adjust_value = 0; |
| switch (recog_memoized (insn)) |
| { |
| case CODE_FOR_call_internal: |
| case CODE_FOR_call_value_internal: |
| { |
| if (NDS32_ALIGN_P ()) |
| { |
| rtx_insn *next_insn = next_active_insn (insn); |
| if (next_insn && get_attr_length (next_insn) != 2) |
| adjust_value += 2; |
| } |
| /* We need insert a nop after a noretun function call |
| to prevent software breakpoint corrupt the next function. */ |
| if (find_reg_note (insn, REG_NORETURN, NULL_RTX)) |
| { |
| if (TARGET_16_BIT) |
| adjust_value += 2; |
| else |
| adjust_value += 4; |
| } |
| } |
| return length + adjust_value; |
| |
| default: |
| return length; |
| } |
| } |
| |
| /* Storage Layout. */ |
| |
| /* This function will be called just before expansion into rtl. */ |
| static void |
| nds32_expand_to_rtl_hook (void) |
| { |
| /* We need to set strictly aligned situation. |
| After that, the memory address checking in nds32_legitimate_address_p() |
| will take alignment offset into consideration so that it will not create |
| unaligned [base + offset] access during the rtl optimization. */ |
| cfun->machine->strict_aligned_p = 1; |
| } |
| |
| |
| /* Register Usage. */ |
| |
| static void |
| nds32_conditional_register_usage (void) |
| { |
| int regno; |
| |
| if (TARGET_HARD_FLOAT) |
| { |
| for (regno = NDS32_FIRST_FPR_REGNUM; |
| regno <= NDS32_LAST_FPR_REGNUM; regno++) |
| { |
| fixed_regs[regno] = 0; |
| if (regno < NDS32_FIRST_FPR_REGNUM + NDS32_MAX_FPR_REGS_FOR_ARGS) |
| call_used_regs[regno] = 1; |
| else if (regno >= NDS32_FIRST_FPR_REGNUM + 22 |
| && regno < NDS32_FIRST_FPR_REGNUM + 48) |
| call_used_regs[regno] = 1; |
| else |
| call_used_regs[regno] = 0; |
| } |
| } |
| else if (TARGET_FPU_SINGLE || TARGET_FPU_DOUBLE) |
| { |
| for (regno = NDS32_FIRST_FPR_REGNUM; |
| regno <= NDS32_LAST_FPR_REGNUM; |
| regno++) |
| fixed_regs[regno] = 0; |
| } |
| } |
| |
| |
| /* Register Classes. */ |
| |
| static unsigned char |
| nds32_class_max_nregs (reg_class_t rclass ATTRIBUTE_UNUSED, |
| machine_mode mode) |
| { |
| /* Return the maximum number of consecutive registers |
| needed to represent "mode" in a register of "rclass". */ |
| return ((GET_MODE_SIZE (mode) + UNITS_PER_WORD - 1) / UNITS_PER_WORD); |
| } |
| |
| static int |
| nds32_register_priority (int hard_regno) |
| { |
| /* Encourage to use r0-r7 for LRA when optimize for size. */ |
| if (optimize_size) |
| { |
| if (hard_regno < 8) |
| return 4; |
| else if (hard_regno < 16) |
| return 3; |
| else if (hard_regno < 28) |
| return 2; |
| else |
| return 1; |
| } |
| else |
| { |
| if (hard_regno > 27) |
| return 1; |
| else |
| return 4; |
| } |
| } |
| |
| static bool |
| nds32_can_change_mode_class (machine_mode from, |
| machine_mode to, |
| reg_class_t rclass) |
| { |
| /* Don't spill double-precision register to two singal-precision |
| registers */ |
| if ((TARGET_FPU_SINGLE || TARGET_FPU_DOUBLE) |
| && GET_MODE_SIZE (from) != GET_MODE_SIZE (to)) |
| { |
| return !reg_classes_intersect_p (rclass, FP_REGS); |
| } |
| |
| return true; |
| } |
| |
| |
| /* Stack Layout and Calling Conventions. */ |
| |
| /* There are three kinds of pointer concepts using in GCC compiler: |
| |
| frame pointer: A pointer to the first location of local variables. |
| stack pointer: A pointer to the top of a stack frame. |
| argument pointer: A pointer to the incoming arguments. |
| |
| In nds32 target calling convention, we are using 8-byte alignment. |
| Besides, we would like to have each stack frame of a function includes: |
| |
| [Block A] |
| 1. previous hard frame pointer |
| 2. return address |
| 3. callee-saved registers |
| 4. <padding bytes> (we will calculte in nds32_compute_stack_frame() |
| and save it at |
| cfun->machine->callee_saved_area_padding_bytes) |
| |
| [Block B] |
| 1. local variables |
| 2. spilling location |
| 3. <padding bytes> (it will be calculated by GCC itself) |
| 4. incoming arguments |
| 5. <padding bytes> (it will be calculated by GCC itself) |
| |
| [Block C] |
| 1. <padding bytes> (it will be calculated by GCC itself) |
| 2. outgoing arguments |
| |
| We 'wrap' these blocks together with |
| hard frame pointer ($r28) and stack pointer ($r31). |
| By applying the basic frame/stack/argument pointers concept, |
| the layout of a stack frame shoule be like this: |
| |
| | | |
| old stack pointer -> ---- |
| | | \ |
| | | saved arguments for |
| | | vararg functions |
| | | / |
| hard frame pointer -> -- |
| & argument pointer | | \ |
| | | previous hardware frame pointer |
| | | return address |
| | | callee-saved registers |
| | | / |
| frame pointer -> -- |
| | | \ |
| | | local variables |
| | | and incoming arguments |
| | | / |
| -- |
| | | \ |
| | | outgoing |
| | | arguments |
| | | / |
| stack pointer -> ---- |
| |
| $SFP and $AP are used to represent frame pointer and arguments pointer, |
| which will be both eliminated as hard frame pointer. */ |
| |
| /* -- Eliminating Frame Pointer and Arg Pointer. */ |
| |
| static bool |
| nds32_can_eliminate (const int from_reg, const int to_reg) |
| { |
| if (from_reg == ARG_POINTER_REGNUM && to_reg == STACK_POINTER_REGNUM) |
| return true; |
| |
| if (from_reg == ARG_POINTER_REGNUM && to_reg == HARD_FRAME_POINTER_REGNUM) |
| return true; |
| |
| if (from_reg == FRAME_POINTER_REGNUM && to_reg == STACK_POINTER_REGNUM) |
| return true; |
| |
| if (from_reg == FRAME_POINTER_REGNUM && to_reg == HARD_FRAME_POINTER_REGNUM) |
| return true; |
| |
| return false; |
| } |
| |
| /* -- Passing Arguments in Registers. */ |
| |
| static rtx |
| nds32_function_arg (cumulative_args_t ca, machine_mode mode, |
| const_tree type, bool named) |
| { |
| unsigned int regno; |
| CUMULATIVE_ARGS *cum = get_cumulative_args (ca); |
| |
| /* The last time this hook is called, |
| it is called with MODE == VOIDmode. */ |
| if (mode == VOIDmode) |
| return NULL_RTX; |
| |
| /* For nameless arguments, we need to take care it individually. */ |
| if (!named) |
| { |
| /* If we are under hard float abi, we have arguments passed on the |
| stack and all situation can be handled by GCC itself. */ |
| if (TARGET_HARD_FLOAT) |
| return NULL_RTX; |
| |
| if (NDS32_ARG_PARTIAL_IN_GPR_REG_P (cum->gpr_offset, mode, type)) |
| { |
| /* If we still have enough registers to pass argument, pick up |
| next available register number. */ |
| regno |
| = NDS32_AVAILABLE_REGNUM_FOR_GPR_ARG (cum->gpr_offset, mode, type); |
| return gen_rtx_REG (mode, regno); |
| } |
| |
| /* No register available, return NULL_RTX. |
| The compiler will use stack to pass argument instead. */ |
| return NULL_RTX; |
| } |
| |
| /* The following is to handle named argument. |
| Note that the strategies of TARGET_HARD_FLOAT and !TARGET_HARD_FLOAT |
| are different. */ |
| if (TARGET_HARD_FLOAT) |
| { |
| /* For TARGET_HARD_FLOAT calling convention, we use GPR and FPR |
| to pass argument. We have to further check TYPE and MODE so |
| that we can determine which kind of register we shall use. */ |
| |
| /* Note that we need to pass argument entirely in registers under |
| hard float abi. */ |
| if (GET_MODE_CLASS (mode) == MODE_FLOAT |
| && NDS32_ARG_ENTIRE_IN_FPR_REG_P (cum->fpr_offset, mode, type)) |
| { |
| /* Pick up the next available FPR register number. */ |
| regno |
| = NDS32_AVAILABLE_REGNUM_FOR_FPR_ARG (cum->fpr_offset, mode, type); |
| return gen_rtx_REG (mode, regno); |
| } |
| else if (GET_MODE_CLASS (mode) != MODE_FLOAT |
| && NDS32_ARG_ENTIRE_IN_GPR_REG_P (cum->gpr_offset, mode, type)) |
| { |
| /* Pick up the next available GPR register number. */ |
| regno |
| = NDS32_AVAILABLE_REGNUM_FOR_GPR_ARG (cum->gpr_offset, mode, type); |
| return gen_rtx_REG (mode, regno); |
| } |
| } |
| else |
| { |
| /* For !TARGET_HARD_FLOAT calling convention, we always use GPR to pass |
| argument. Since we allow to pass argument partially in registers, |
| we can just return it if there are still registers available. */ |
| if (NDS32_ARG_PARTIAL_IN_GPR_REG_P (cum->gpr_offset, mode, type)) |
| { |
| /* Pick up the next available register number. */ |
| regno |
| = NDS32_AVAILABLE_REGNUM_FOR_GPR_ARG (cum->gpr_offset, mode, type); |
| return gen_rtx_REG (mode, regno); |
| } |
| |
| } |
| |
| /* No register available, return NULL_RTX. |
| The compiler will use stack to pass argument instead. */ |
| return NULL_RTX; |
| } |
| |
| static bool |
| nds32_must_pass_in_stack (machine_mode mode, const_tree type) |
| { |
| /* Return true if a type must be passed in memory. |
| If it is NOT using hard float abi, small aggregates can be |
| passed in a register even we are calling a variadic function. |
| So there is no need to take padding into consideration. */ |
| if (TARGET_HARD_FLOAT) |
| return must_pass_in_stack_var_size_or_pad (mode, type); |
| else |
| return must_pass_in_stack_var_size (mode, type); |
| } |
| |
| static int |
| nds32_arg_partial_bytes (cumulative_args_t ca, machine_mode mode, |
| tree type, bool named ATTRIBUTE_UNUSED) |
| { |
| /* Returns the number of bytes at the beginning of an argument that |
| must be put in registers. The value must be zero for arguments that are |
| passed entirely in registers or that are entirely pushed on the stack. |
| Besides, TARGET_FUNCTION_ARG for these arguments should return the |
| first register to be used by the caller for this argument. */ |
| unsigned int needed_reg_count; |
| unsigned int remaining_reg_count; |
| CUMULATIVE_ARGS *cum; |
| |
| cum = get_cumulative_args (ca); |
| |
| /* Under hard float abi, we better have argument entirely passed in |
| registers or pushed on the stack so that we can reduce the complexity |
| of dealing with cum->gpr_offset and cum->fpr_offset. */ |
| if (TARGET_HARD_FLOAT) |
| return 0; |
| |
| /* If we have already runned out of argument registers, return zero |
| so that the argument will be entirely pushed on the stack. */ |
| if (NDS32_AVAILABLE_REGNUM_FOR_GPR_ARG (cum->gpr_offset, mode, type) |
| >= NDS32_GPR_ARG_FIRST_REGNUM + NDS32_MAX_GPR_REGS_FOR_ARGS) |
| return 0; |
| |
| /* Calculate how many registers do we need for this argument. */ |
| needed_reg_count = NDS32_NEED_N_REGS_FOR_ARG (mode, type); |
| |
| /* Calculate how many argument registers have left for passing argument. |
| Note that we should count it from next available register number. */ |
| remaining_reg_count |
| = NDS32_MAX_GPR_REGS_FOR_ARGS |
| - (NDS32_AVAILABLE_REGNUM_FOR_GPR_ARG (cum->gpr_offset, mode, type) |
| - NDS32_GPR_ARG_FIRST_REGNUM); |
| |
| /* Note that we have to return the nubmer of bytes, not registers count. */ |
| if (needed_reg_count > remaining_reg_count) |
| return remaining_reg_count * UNITS_PER_WORD; |
| |
| return 0; |
| } |
| |
| static void |
| nds32_function_arg_advance (cumulative_args_t ca, machine_mode mode, |
| const_tree type, bool named) |
| { |
| CUMULATIVE_ARGS *cum = get_cumulative_args (ca); |
| |
| if (named) |
| { |
| /* We need to further check TYPE and MODE so that we can determine |
| which kind of register we shall advance. */ |
| |
| /* Under hard float abi, we may advance FPR registers. */ |
| if (TARGET_HARD_FLOAT && GET_MODE_CLASS (mode) == MODE_FLOAT) |
| { |
| cum->fpr_offset |
| = NDS32_AVAILABLE_REGNUM_FOR_FPR_ARG (cum->fpr_offset, mode, type) |
| - NDS32_FPR_ARG_FIRST_REGNUM |
| + NDS32_NEED_N_REGS_FOR_ARG (mode, type); |
| } |
| else |
| { |
| cum->gpr_offset |
| = NDS32_AVAILABLE_REGNUM_FOR_GPR_ARG (cum->gpr_offset, mode, type) |
| - NDS32_GPR_ARG_FIRST_REGNUM |
| + NDS32_NEED_N_REGS_FOR_ARG (mode, type); |
| } |
| } |
| else |
| { |
| /* If this nameless argument is NOT under TARGET_HARD_FLOAT, |
| we can advance next register as well so that caller is |
| able to pass arguments in registers and callee must be |
| in charge of pushing all of them into stack. */ |
| if (!TARGET_HARD_FLOAT) |
| { |
| cum->gpr_offset |
| = NDS32_AVAILABLE_REGNUM_FOR_GPR_ARG (cum->gpr_offset, mode, type) |
| - NDS32_GPR_ARG_FIRST_REGNUM |
| + NDS32_NEED_N_REGS_FOR_ARG (mode, type); |
| } |
| } |
| } |
| |
| static unsigned int |
| nds32_function_arg_boundary (machine_mode mode, const_tree type) |
| { |
| return (nds32_needs_double_word_align (mode, type) |
| ? NDS32_DOUBLE_WORD_ALIGNMENT |
| : PARM_BOUNDARY); |
| } |
| |
| /* -- How Scalar Function Values Are Returned. */ |
| |
| static rtx |
| nds32_function_value (const_tree ret_type, |
| const_tree fn_decl_or_type ATTRIBUTE_UNUSED, |
| bool outgoing ATTRIBUTE_UNUSED) |
| { |
| machine_mode mode; |
| int unsignedp; |
| |
| mode = TYPE_MODE (ret_type); |
| unsignedp = TYPE_UNSIGNED (ret_type); |
| |
| if (INTEGRAL_TYPE_P (ret_type)) |
| mode = promote_mode (ret_type, mode, &unsignedp); |
| |
| if (TARGET_HARD_FLOAT && (mode == SFmode || mode == DFmode)) |
| return gen_rtx_REG (mode, NDS32_FPR_RET_FIRST_REGNUM); |
| else |
| return gen_rtx_REG (mode, NDS32_GPR_RET_FIRST_REGNUM); |
| } |
| |
| static rtx |
| nds32_libcall_value (machine_mode mode, |
| const_rtx fun ATTRIBUTE_UNUSED) |
| { |
| if (TARGET_HARD_FLOAT && (mode == SFmode || mode == DFmode)) |
| return gen_rtx_REG (mode, NDS32_FPR_RET_FIRST_REGNUM); |
| |
| return gen_rtx_REG (mode, NDS32_GPR_RET_FIRST_REGNUM); |
| } |
| |
| static bool |
| nds32_function_value_regno_p (const unsigned int regno) |
| { |
| if (regno == NDS32_GPR_RET_FIRST_REGNUM |
| || (TARGET_HARD_FLOAT |
| && regno == NDS32_FPR_RET_FIRST_REGNUM)) |
| return true; |
| |
| return false; |
| } |
| |
| /* -- How Large Values Are Returned. */ |
| |
| static bool |
| nds32_return_in_memory (const_tree type, |
| const_tree fntype ATTRIBUTE_UNUSED) |
| { |
| /* Note that int_size_in_bytes can return -1 if the size can vary |
| or is larger than an integer. */ |
| HOST_WIDE_INT size = int_size_in_bytes (type); |
| |
| /* For COMPLEX_TYPE, if the total size cannot be hold within two registers, |
| the return value is supposed to be in memory. We need to be aware of |
| that the size may be -1. */ |
| if (TREE_CODE (type) == COMPLEX_TYPE) |
| if (size < 0 || size > 2 * UNITS_PER_WORD) |
| return true; |
| |
| /* If it is BLKmode and the total size cannot be hold within two registers, |
| the return value is supposed to be in memory. We need to be aware of |
| that the size may be -1. */ |
| if (TYPE_MODE (type) == BLKmode) |
| if (size < 0 || size > 2 * UNITS_PER_WORD) |
| return true; |
| |
| /* For other cases, having result in memory is unnecessary. */ |
| return false; |
| } |
| |
| /* -- Function Entry and Exit. */ |
| |
| /* The content produced from this function |
| will be placed before prologue body. */ |
| static void |
| nds32_asm_function_prologue (FILE *file) |
| { |
| int r; |
| const char *func_name; |
| tree attrs; |
| tree name; |
| |
| /* All stack frame information is supposed to be |
| already computed when expanding prologue. |
| The result is in cfun->machine. |
| DO NOT call nds32_compute_stack_frame() here |
| because it may corrupt the essential information. */ |
| |
| fprintf (file, "\t! BEGIN PROLOGUE\n"); |
| fprintf (file, "\t! fp needed: %d\n", frame_pointer_needed); |
| fprintf (file, "\t! pretend_args: %d\n", cfun->machine->va_args_size); |
| fprintf (file, "\t! local_size: %d\n", cfun->machine->local_size); |
| fprintf (file, "\t! out_args_size: %d\n", cfun->machine->out_args_size); |
| |
| /* Use df_regs_ever_live_p() to detect if the register |
| is ever used in the current function. */ |
| fprintf (file, "\t! registers ever_live: "); |
| for (r = 0; r < 65; r++) |
| { |
| if (df_regs_ever_live_p (r)) |
| fprintf (file, "%s, ", reg_names[r]); |
| } |
| fputc ('\n', file); |
| |
| /* Display the attributes of this function. */ |
| fprintf (file, "\t! function attributes: "); |
| /* Get the attributes tree list. |
| Note that GCC builds attributes list with reverse order. */ |
| attrs = DECL_ATTRIBUTES (current_function_decl); |
| |
| /* If there is no any attribute, print out "None". */ |
| if (!attrs) |
| fprintf (file, "None"); |
| |
| /* If there are some attributes, try if we need to |
| construct isr vector information. */ |
| func_name = IDENTIFIER_POINTER (DECL_NAME (current_function_decl)); |
| nds32_construct_isr_vectors_information (attrs, func_name); |
| |
| /* Display all attributes of this function. */ |
| while (attrs) |
| { |
| name = TREE_PURPOSE (attrs); |
| fprintf (file, "%s ", IDENTIFIER_POINTER (name)); |
| |
| /* Pick up the next attribute. */ |
| attrs = TREE_CHAIN (attrs); |
| } |
| fputc ('\n', file); |
| } |
| |
| /* After rtl prologue has been expanded, this function is used. */ |
| static void |
| nds32_asm_function_end_prologue (FILE *file) |
| { |
| fprintf (file, "\t! END PROLOGUE\n"); |
| |
| /* If frame pointer is NOT needed and -mfp-as-gp is issued, |
| we can generate special directive: ".omit_fp_begin" |
| to guide linker doing fp-as-gp optimization. |
| However, for a naked function, which means |
| it should not have prologue/epilogue, |
| using fp-as-gp still requires saving $fp by push/pop behavior and |
| there is no benefit to use fp-as-gp on such small function. |
| So we need to make sure this function is NOT naked as well. */ |
| if (!frame_pointer_needed |
| && !cfun->machine->naked_p |
| && cfun->machine->fp_as_gp_p) |
| { |
| fprintf (file, "\t! ----------------------------------------\n"); |
| fprintf (file, "\t! Guide linker to do " |
| "link time optimization: fp-as-gp\n"); |
| fprintf (file, "\t! We add one more instruction to " |
| "initialize $fp near to $gp location.\n"); |
| fprintf (file, "\t! If linker fails to use fp-as-gp transformation,\n"); |
| fprintf (file, "\t! this extra instruction should be " |
| "eliminated at link stage.\n"); |
| fprintf (file, "\t.omit_fp_begin\n"); |
| fprintf (file, "\tla\t$fp,_FP_BASE_\n"); |
| fprintf (file, "\t! ----------------------------------------\n"); |
| } |
| } |
| |
| /* Before rtl epilogue has been expanded, this function is used. */ |
| static void |
| nds32_asm_function_begin_epilogue (FILE *file) |
| { |
| /* If frame pointer is NOT needed and -mfp-as-gp is issued, |
| we can generate special directive: ".omit_fp_end" |
| to claim fp-as-gp optimization range. |
| However, for a naked function, |
| which means it should not have prologue/epilogue, |
| using fp-as-gp still requires saving $fp by push/pop behavior and |
| there is no benefit to use fp-as-gp on such small function. |
| So we need to make sure this function is NOT naked as well. */ |
| if (!frame_pointer_needed |
| && !cfun->machine->naked_p |
| && cfun->machine->fp_as_gp_p) |
| { |
| fprintf (file, "\t! ----------------------------------------\n"); |
| fprintf (file, "\t! Claim the range of fp-as-gp " |
| "link time optimization\n"); |
| fprintf (file, "\t.omit_fp_end\n"); |
| fprintf (file, "\t! ----------------------------------------\n"); |
| } |
| |
| fprintf (file, "\t! BEGIN EPILOGUE\n"); |
| } |
| |
| /* The content produced from this function |
| will be placed after epilogue body. */ |
| static void |
| nds32_asm_function_epilogue (FILE *file) |
| { |
| fprintf (file, "\t! END EPILOGUE\n"); |
| } |
| |
| static void |
| nds32_asm_output_mi_thunk (FILE *file, tree thunk ATTRIBUTE_UNUSED, |
| HOST_WIDE_INT delta, |
| HOST_WIDE_INT vcall_offset ATTRIBUTE_UNUSED, |
| tree function) |
| { |
| int this_regno; |
| |
| /* Make sure unwind info is emitted for the thunk if needed. */ |
| final_start_function (emit_barrier (), file, 1); |
| |
| this_regno = (aggregate_value_p (TREE_TYPE (TREE_TYPE (function)), function) |
| ? 1 |
| : 0); |
| |
| if (delta != 0) |
| { |
| if (satisfies_constraint_Is15 (GEN_INT (delta))) |
| { |
| fprintf (file, "\taddi\t$r%d, $r%d, " HOST_WIDE_INT_PRINT_DEC "\n", |
| this_regno, this_regno, delta); |
| } |
| else if (satisfies_constraint_Is20 (GEN_INT (delta))) |
| { |
| fprintf (file, "\tmovi\t$ta, " HOST_WIDE_INT_PRINT_DEC "\n", delta); |
| fprintf (file, "\tadd\t$r%d, $r%d, $ta\n", this_regno, this_regno); |
| } |
| else |
| { |
| fprintf (file, |
| "\tsethi\t$ta, hi20(" HOST_WIDE_INT_PRINT_DEC ")\n", |
| delta); |
| fprintf (file, |
| "\tori\t$ta, $ta, lo12(" HOST_WIDE_INT_PRINT_DEC ")\n", |
| delta); |
| fprintf (file, "\tadd\t$r%d, $r%d, $ta\n", this_regno, this_regno); |
| } |
| } |
| |
| fprintf (file, "\tb\t"); |
| assemble_name (file, XSTR (XEXP (DECL_RTL (function), 0), 0)); |
| fprintf (file, "\n"); |
| |
| final_end_function (); |
| } |
| |
| /* -- Permitting tail calls. */ |
| |
| /* Return true if it is ok to do sibling call optimization. */ |
| static bool |
| nds32_function_ok_for_sibcall (tree decl, |
| tree exp ATTRIBUTE_UNUSED) |
| { |
| /* The DECL is NULL if it is an indirect call. */ |
| |
| /* 1. Do not apply sibling call if -mv3push is enabled, |
| because pop25 instruction also represents return behavior. |
| 2. If this function is a variadic function, do not apply sibling call |
| because the stack layout may be a mess. |
| 3. We don't want to apply sibling call optimization for indirect |
| sibcall because the pop behavior in epilogue may pollute the |
| content of caller-saved regsiter when the register is used for |
| indirect sibcall. */ |
| return (!TARGET_V3PUSH |
| && (cfun->machine->va_args_size == 0) |
| && decl); |
| } |
| |
| /* Determine whether we need to enable warning for function return check. */ |
| static bool |
| nds32_warn_func_return (tree decl) |
| { |
| /* Naked functions are implemented entirely in assembly, including the |
| return sequence, so suppress warnings about this. */ |
| return !nds32_naked_function_p (decl); |
| } |
| |
| |
| /* Implementing the Varargs Macros. */ |
| |
| static void |
| nds32_setup_incoming_varargs (cumulative_args_t ca, |
| machine_mode mode, |
| tree type, |
| int *pretend_args_size, |
| int second_time ATTRIBUTE_UNUSED) |
| { |
| unsigned int total_args_regs; |
| unsigned int num_of_used_regs; |
| unsigned int remaining_reg_count; |
| CUMULATIVE_ARGS *cum; |
| |
| /* If we are under hard float abi, we do not need to set *pretend_args_size. |
| So that all nameless arguments are pushed by caller and all situation |
| can be handled by GCC itself. */ |
| if (TARGET_HARD_FLOAT) |
| return; |
| |
| /* We are using NDS32_MAX_GPR_REGS_FOR_ARGS registers, |
| counting from NDS32_GPR_ARG_FIRST_REGNUM, for saving incoming arguments. |
| However, for nameless(anonymous) arguments, we should push them on the |
| stack so that all the nameless arguments appear to have been passed |
| consecutively in the memory for accessing. Hence, we need to check and |
| exclude the registers that are used for named arguments. */ |
| |
| cum = get_cumulative_args (ca); |
| |
| /* The MODE and TYPE describe the last argument. |
| We need those information to determine the remaining registers |
| for varargs. */ |
| total_args_regs |
| = NDS32_MAX_GPR_REGS_FOR_ARGS + NDS32_GPR_ARG_FIRST_REGNUM; |
| num_of_used_regs |
| = NDS32_AVAILABLE_REGNUM_FOR_GPR_ARG (cum->gpr_offset, mode, type) |
| + NDS32_NEED_N_REGS_FOR_ARG (mode, type); |
| |
| remaining_reg_count = total_args_regs - num_of_used_regs; |
| *pretend_args_size = remaining_reg_count * UNITS_PER_WORD; |
| |
| return; |
| } |
| |
| static bool |
| nds32_strict_argument_naming (cumulative_args_t ca ATTRIBUTE_UNUSED) |
| { |
| /* If this hook returns true, the named argument of FUNCTION_ARG is always |
| true for named arguments, and false for unnamed arguments. */ |
| return true; |
| } |
| |
| |
| /* Trampolines for Nested Functions. */ |
| |
| static void |
| nds32_asm_trampoline_template (FILE *f) |
| { |
| if (TARGET_REDUCED_REGS) |
| { |
| /* Trampoline is not supported on reduced-set registers yet. */ |
| sorry ("a nested function is not supported for reduced registers"); |
| } |
| else |
| { |
| asm_fprintf (f, "\t! Trampoline code template\n"); |
| asm_fprintf (f, "\t! This code fragment will be copied " |
| "into stack on demand\n"); |
| |
| asm_fprintf (f, "\tmfusr\t$r16,$pc\n"); |
| asm_fprintf (f, "\tlwi\t$r15,[$r16 + 20] " |
| "! load nested function address\n"); |
| asm_fprintf (f, "\tlwi\t$r16,[$r16 + 16] " |
| "! load chain_value\n"); |
| asm_fprintf (f, "\tjr\t$r15\n"); |
| } |
| |
| /* Preserve space ($pc + 16) for saving chain_value, |
| nds32_trampoline_init will fill the value in this slot. */ |
| asm_fprintf (f, "\t! space for saving chain_value\n"); |
| assemble_aligned_integer (UNITS_PER_WORD, const0_rtx); |
| |
| /* Preserve space ($pc + 20) for saving nested function address, |
| nds32_trampoline_init will fill the value in this slot. */ |
| asm_fprintf (f, "\t! space for saving nested function address\n"); |
| assemble_aligned_integer (UNITS_PER_WORD, const0_rtx); |
| } |
| |
| /* Emit RTL insns to initialize the variable parts of a trampoline. */ |
| static void |
| nds32_trampoline_init (rtx m_tramp, tree fndecl, rtx chain_value) |
| { |
| int i; |
| |
| /* Nested function address. */ |
| rtx fnaddr; |
| /* The memory rtx that is going to |
| be filled with chain_value. */ |
| rtx chain_value_mem; |
| /* The memory rtx that is going to |
| be filled with nested function address. */ |
| rtx nested_func_mem; |
| |
| /* Start address of trampoline code in stack, for doing cache sync. */ |
| rtx sync_cache_addr; |
| /* Temporary register for sync instruction. */ |
| rtx tmp_reg; |
| /* Instruction-cache sync instruction, |
| requesting an argument as starting address. */ |
| rtx isync_insn; |
| /* For convenience reason of doing comparison. */ |
| int tramp_align_in_bytes; |
| |
| /* Trampoline is not supported on reduced-set registers yet. */ |
| if (TARGET_REDUCED_REGS) |
| sorry ("a nested function is not supported for reduced registers"); |
| |
| /* STEP 1: Copy trampoline code template into stack, |
| fill up essential data into stack. */ |
| |
| /* Extract nested function address rtx. */ |
| fnaddr = XEXP (DECL_RTL (fndecl), 0); |
| |
| /* m_tramp is memory rtx that is going to be filled with trampoline code. |
| We have nds32_asm_trampoline_template() to emit template pattern. */ |
| emit_block_move (m_tramp, assemble_trampoline_template (), |
| GEN_INT (TRAMPOLINE_SIZE), BLOCK_OP_NORMAL); |
| |
| /* After copying trampoline code into stack, |
| fill chain_value into stack. */ |
| chain_value_mem = adjust_address (m_tramp, SImode, 16); |
| emit_move_insn (chain_value_mem, chain_value); |
| /* After copying trampoline code int stack, |
| fill nested function address into stack. */ |
| nested_func_mem = adjust_address (m_tramp, SImode, 20); |
| emit_move_insn (nested_func_mem, fnaddr); |
| |
| /* STEP 2: Sync instruction-cache. */ |
| |
| /* We have successfully filled trampoline code into stack. |
| However, in order to execute code in stack correctly, |
| we must sync instruction cache. */ |
| sync_cache_addr = XEXP (m_tramp, 0); |
| tmp_reg = gen_reg_rtx (SImode); |
| isync_insn = gen_unspec_volatile_isync (tmp_reg); |
| |
| /* Because nds32_cache_block_size is in bytes, |
| we get trampoline alignment in bytes for convenient comparison. */ |
| tramp_align_in_bytes = TRAMPOLINE_ALIGNMENT / BITS_PER_UNIT; |
| |
| if (tramp_align_in_bytes >= nds32_cache_block_size |
| && (tramp_align_in_bytes % nds32_cache_block_size) == 0) |
| { |
| /* Under this condition, the starting address of trampoline |
| must be aligned to the starting address of each cache block |
| and we do not have to worry about cross-boundary issue. */ |
| for (i = 0; |
| i < (TRAMPOLINE_SIZE + nds32_cache_block_size - 1) |
| / nds32_cache_block_size; |
| i++) |
| { |
| emit_move_insn (tmp_reg, |
| plus_constant (Pmode, sync_cache_addr, |
| nds32_cache_block_size * i)); |
| emit_insn (isync_insn); |
| } |
| } |
| else if (TRAMPOLINE_SIZE > nds32_cache_block_size) |
| { |
| /* The starting address of trampoline code |
| may not be aligned to the cache block, |
| so the trampoline code may be across two cache block. |
| We need to sync the last element, which is 4-byte size, |
| of trampoline template. */ |
| for (i = 0; |
| i < (TRAMPOLINE_SIZE + nds32_cache_block_size - 1) |
| / nds32_cache_block_size; |
| i++) |
| { |
| emit_move_insn (tmp_reg, |
| plus_constant (Pmode, sync_cache_addr, |
| nds32_cache_block_size * i)); |
| emit_insn (isync_insn); |
| } |
| |
| /* The last element of trampoline template is 4-byte size. */ |
| emit_move_insn (tmp_reg, |
| plus_constant (Pmode, sync_cache_addr, |
| TRAMPOLINE_SIZE - 4)); |
| emit_insn (isync_insn); |
| } |
| else |
| { |
| /* This is the simplest case. |
| Because TRAMPOLINE_SIZE is less than or |
| equal to nds32_cache_block_size, |
| we can just sync start address and |
| the last element of trampoline code. */ |
| |
| /* Sync starting address of tampoline code. */ |
| emit_move_insn (tmp_reg, sync_cache_addr); |
| emit_insn (isync_insn); |
| /* Sync the last element, which is 4-byte size, |
| of trampoline template. */ |
| emit_move_insn (tmp_reg, |
| plus_constant (Pmode, sync_cache_addr, |
| TRAMPOLINE_SIZE - 4)); |
| emit_insn (isync_insn); |
| } |
| |
| /* Set instruction serialization barrier |
| to guarantee the correct operations. */ |
| emit_insn (gen_unspec_volatile_isb ()); |
| } |
| |
| |
| /* Addressing Modes. */ |
| |
| static bool |
| nds32_legitimate_address_p (machine_mode mode, rtx x, bool strict) |
| { |
| if (TARGET_FPU_SINGLE || TARGET_FPU_DOUBLE) |
| { |
| /* When using floating-point instructions, |
| we don't allow 'addr' to be [symbol_ref], [CONST] pattern. */ |
| if ((mode == DFmode || mode == SFmode) |
| && (GET_CODE (x) == SYMBOL_REF |
| || GET_CODE(x) == CONST)) |
| return false; |
| |
| /* Allow [post_modify] addressing mode, when using FPU instructions. */ |
| if (GET_CODE (x) == POST_MODIFY |
| && mode == DFmode) |
| { |
| if (GET_CODE (XEXP (x, 0)) == REG |
| && GET_CODE (XEXP (x, 1)) == PLUS) |
| { |
| rtx plus_op = XEXP (x, 1); |
| rtx op0 = XEXP (plus_op, 0); |
| rtx op1 = XEXP (plus_op, 1); |
| |
| if (nds32_address_register_rtx_p (op0, strict) |
| && CONST_INT_P (op1)) |
| { |
| if (satisfies_constraint_Is14 (op1)) |
| { |
| /* If it is not under strictly aligned situation, |
| we can return true without checking alignment. */ |
| if (!cfun->machine->strict_aligned_p) |
| return true; |
| /* Make sure address is word alignment. |
| Currently we do not have 64-bit load/store yet, |
| so we will use two 32-bit load/store instructions to do |
| memory access and they are single word alignment. */ |
| else if (NDS32_SINGLE_WORD_ALIGN_P (INTVAL (op1))) |
| return true; |
| } |
| } |
| } |
| } |
| } |
| |
| /* For (mem:DI addr) or (mem:DF addr) case, |
| we only allow 'addr' to be [reg], [symbol_ref], |
| [const], or [reg + const_int] pattern. */ |
| if (mode == DImode || mode == DFmode) |
| { |
| /* Allow [Reg + const_int] addressing mode. */ |
| if (GET_CODE (x) == PLUS) |
| { |
| if (nds32_address_register_rtx_p (XEXP (x, 0), strict) |
| && nds32_legitimate_index_p (mode, XEXP (x, 1), strict) |
| && CONST_INT_P (XEXP (x, 1))) |
| return true; |
| else if (nds32_address_register_rtx_p (XEXP (x, 1), strict) |
| && nds32_legitimate_index_p (mode, XEXP (x, 0), strict) |
| && CONST_INT_P (XEXP (x, 0))) |
| return true; |
| } |
| |
| /* Allow [post_inc] and [post_dec] addressing mode. */ |
| if (GET_CODE (x) == POST_INC || GET_CODE (x) == POST_DEC) |
| { |
| if (nds32_address_register_rtx_p (XEXP (x, 0), strict)) |
| return true; |
| } |
| |
| /* Now check [reg], [symbol_ref], and [const]. */ |
| if (GET_CODE (x) != REG |
| && GET_CODE (x) != SYMBOL_REF |
| && GET_CODE (x) != CONST) |
| return false; |
| } |
| |
| /* Check if 'x' is a valid address. */ |
| switch (GET_CODE (x)) |
| { |
| case REG: |
| /* (mem (reg A)) => [Ra] */ |
| return nds32_address_register_rtx_p (x, strict); |
| |
| case SYMBOL_REF: |
| /* (mem (symbol_ref A)) => [symbol_ref] */ |
| /* If -mcmodel=large, the 'symbol_ref' is not a valid address |
| during or after LRA/reload phase. */ |
| if (TARGET_CMODEL_LARGE |
| && (reload_completed |
| || reload_in_progress |
| || lra_in_progress)) |
| return false; |
| /* If -mcmodel=medium and the symbol references to rodata section, |
| the 'symbol_ref' is not a valid address during or after |
| LRA/reload phase. */ |
| if (TARGET_CMODEL_MEDIUM |
| && NDS32_SYMBOL_REF_RODATA_P (x) |
| && (reload_completed |
| || reload_in_progress |
| || lra_in_progress)) |
| return false; |
| |
| return true; |
| |
| case CONST: |
| /* (mem (const (...))) |
| => [ + const_addr ], where const_addr = symbol_ref + const_int */ |
| if (GET_CODE (XEXP (x, 0)) == PLUS) |
| { |
| rtx plus_op = XEXP (x, 0); |
| |
| rtx op0 = XEXP (plus_op, 0); |
| rtx op1 = XEXP (plus_op, 1); |
| |
| if (GET_CODE (op0) == SYMBOL_REF && CONST_INT_P (op1)) |
| { |
| /* Now we see the [ + const_addr ] pattern, but we need |
| some further checking. */ |
| /* If -mcmodel=large, the 'const_addr' is not a valid address |
| during or after LRA/reload phase. */ |
| if (TARGET_CMODEL_LARGE |
| && (reload_completed |
| || reload_in_progress |
| || lra_in_progress)) |
| return false; |
| /* If -mcmodel=medium and the symbol references to rodata section, |
| the 'const_addr' is not a valid address during or after |
| LRA/reload phase. */ |
| if (TARGET_CMODEL_MEDIUM |
| && NDS32_SYMBOL_REF_RODATA_P (op0) |
| && (reload_completed |
| || reload_in_progress |
| || lra_in_progress)) |
| return false; |
| |
| /* At this point we can make sure 'const_addr' is a |
| valid address. */ |
| return true; |
| } |
| } |
| |
| return false; |
| |
| case POST_MODIFY: |
| /* (mem (post_modify (reg) (plus (reg) (reg)))) |
| => [Ra], Rb */ |
| /* (mem (post_modify (reg) (plus (reg) (const_int)))) |
| => [Ra], const_int */ |
| if (GET_CODE (XEXP (x, 0)) == REG |
| && GET_CODE (XEXP (x, 1)) == PLUS) |
| { |
| rtx plus_op = XEXP (x, 1); |
| |
| rtx op0 = XEXP (plus_op, 0); |
| rtx op1 = XEXP (plus_op, 1); |
| |
| if (nds32_address_register_rtx_p (op0, strict) |
| && nds32_legitimate_index_p (mode, op1, strict)) |
| return true; |
| else |
| return false; |
| } |
| |
| return false; |
| |
| case POST_INC: |
| case POST_DEC: |
| /* (mem (post_inc reg)) => [Ra], 1/2/4 */ |
| /* (mem (post_dec reg)) => [Ra], -1/-2/-4 */ |
| /* The 1/2/4 or -1/-2/-4 have been displayed in nds32.md. |
| We only need to deal with register Ra. */ |
| if (nds32_address_register_rtx_p (XEXP (x, 0), strict)) |
| return true; |
| else |
| return false; |
| |
| case PLUS: |
| /* (mem (plus reg const_int)) |
| => [Ra + imm] */ |
| /* (mem (plus reg reg)) |
| => [Ra + Rb] */ |
| /* (mem (plus (mult reg const_int) reg)) |
| => [Ra + Rb << sv] */ |
| if (nds32_address_register_rtx_p (XEXP (x, 0), strict) |
| && nds32_legitimate_index_p (mode, XEXP (x, 1), strict)) |
| return true; |
| else if (nds32_address_register_rtx_p (XEXP (x, 1), strict) |
| && nds32_legitimate_index_p (mode, XEXP (x, 0), strict)) |
| return true; |
| else |
| return false; |
| |
| case LO_SUM: |
| /* (mem (lo_sum (reg) (symbol_ref))) */ |
| /* (mem (lo_sum (reg) (const))) */ |
| gcc_assert (REG_P (XEXP (x, 0))); |
| if (GET_CODE (XEXP (x, 1)) == SYMBOL_REF |
| || GET_CODE (XEXP (x, 1)) == CONST) |
| return nds32_legitimate_address_p (mode, XEXP (x, 1), strict); |
| else |
| return false; |
| |
| default: |
| return false; |
| } |
| } |
| |
| |
| /* Condition Code Status. */ |
| |
| /* -- Representation of condition codes using registers. */ |
| |
| static void |
| nds32_canonicalize_comparison (int *code, |
| rtx *op0 ATTRIBUTE_UNUSED, |
| rtx *op1, |
| bool op0_preserve_value ATTRIBUTE_UNUSED) |
| { |
| /* When the instruction combination pass tries to combine a comparison insn |
| with its previous insns, it also transforms the operator in order to |
| minimize its constant field. For example, it tries to transform a |
| comparison insn from |
| (set (reg:SI 54) |
| (ltu:SI (reg:SI 52) |
| (const_int 10 [0xa]))) |
| to |
| (set (reg:SI 54) |
| (leu:SI (reg:SI 52) |
| (const_int 9 [0x9]))) |
| |
| However, the nds32 target only provides instructions supporting the LTU |
| operation directly, and the implementation of the pattern "cbranchsi4" |
| only expands the LTU form. In order to handle the non-LTU operations |
| generated from passes other than the RTL expansion pass, we have to |
| implement this hook to revert those changes. Since we only expand the LTU |
| operator in the RTL expansion pass, we might only need to handle the LEU |
| case, unless we find other optimization passes perform more aggressive |
| transformations. */ |
| |
| if (*code == LEU && CONST_INT_P (*op1)) |
| { |
| *op1 = gen_int_mode (INTVAL (*op1) + 1, SImode); |
| *code = LTU; |
| } |
| } |
| |
| |
| /* Describing Relative Costs of Operations. */ |
| |
| static int |
| nds32_register_move_cost (machine_mode mode ATTRIBUTE_UNUSED, |
| reg_class_t from, |
| reg_class_t to) |
| { |
| if ((from == FP_REGS && to != FP_REGS) |
| || (from != FP_REGS && to == FP_REGS)) |
| return 9; |
| else if (from == HIGH_REGS || to == HIGH_REGS) |
| return optimize_size ? 6 : 2; |
| else |
| return 2; |
| } |
| |
| static int |
| nds32_memory_move_cost (machine_mode mode ATTRIBUTE_UNUSED, |
| reg_class_t rclass ATTRIBUTE_UNUSED, |
| bool in ATTRIBUTE_UNUSED) |
| { |
| return 8; |
| } |
| |
| /* This target hook describes the relative costs of RTL expressions. |
| Return 'true' when all subexpressions of x have been processed. |
| Return 'false' to sum the costs of sub-rtx, plus cost of this operation. |
| Refer to gcc/rtlanal.c for more information. */ |
| static bool |
| nds32_rtx_costs (rtx x, |
| machine_mode mode, |
| int outer_code, |
| int opno, |
| int *total, |
| bool speed) |
| { |
| return nds32_rtx_costs_impl (x, mode, outer_code, opno, total, speed); |
| } |
| |
| static int |
| nds32_address_cost (rtx address, |
| machine_mode mode, |
| addr_space_t as, |
| bool speed) |
| { |
| return nds32_address_cost_impl (address, mode, as, speed); |
| } |
| |
| |
| /* Dividing the Output into Sections (Texts, Data, . . . ). */ |
| |
| /* If references to a symbol or a constant must be treated differently |
| depending on something about the variable or function named by the symbol |
| (such as what section it is in), we use this hook to store flags |
| in symbol_ref rtx. */ |
| static void |
| nds32_encode_section_info (tree decl, rtx rtl, int new_decl_p) |
| { |
| default_encode_section_info (decl, rtl, new_decl_p); |
| |
| /* For the memory rtx, if it references to rodata section, we can store |
| NDS32_SYMBOL_FLAG_RODATA flag into symbol_ref rtx so that the |
| nds32_legitimate_address_p() can determine how to treat such symbol_ref |
| based on -mcmodel=X and this information. */ |
| if (MEM_P (rtl) && MEM_READONLY_P (rtl)) |
| { |
| rtx addr = XEXP (rtl, 0); |
| |
| if (GET_CODE (addr) == SYMBOL_REF) |
| { |
| /* For (mem (symbol_ref X)) case. */ |
| SYMBOL_REF_FLAGS (addr) |= NDS32_SYMBOL_FLAG_RODATA; |
| } |
| else if (GET_CODE (addr) == CONST |
| && GET_CODE (XEXP (addr, 0)) == PLUS) |
| { |
| /* For (mem (const (plus (symbol_ref X) (const_int N)))) case. */ |
| rtx plus_op = XEXP (addr, 0); |
| rtx op0 = XEXP (plus_op, 0); |
| rtx op1 = XEXP (plus_op, 1); |
| |
| if (GET_CODE (op0) == SYMBOL_REF && CONST_INT_P (op1)) |
| SYMBOL_REF_FLAGS (op0) |= NDS32_SYMBOL_FLAG_RODATA; |
| } |
| } |
| } |
| |
| |
| /* Defining the Output Assembler Language. */ |
| |
| /* -- The Overall Framework of an Assembler File. */ |
| |
| static void |
| nds32_asm_file_start (void) |
| { |
| default_file_start (); |
| |
| /* Tell assembler which ABI we are using. */ |
| fprintf (asm_out_file, "\t! ABI version\n"); |
| if (TARGET_HARD_FLOAT) |
| fprintf (asm_out_file, "\t.abi_2fp_plus\n"); |
| else |
| fprintf (asm_out_file, "\t.abi_2\n"); |
| |
| /* Tell assembler that this asm code is generated by compiler. */ |
| fprintf (asm_out_file, "\t! This asm file is generated by compiler\n"); |
| fprintf (asm_out_file, "\t.flag\tverbatim\n"); |
| /* Give assembler the size of each vector for interrupt handler. */ |
| fprintf (asm_out_file, "\t! This vector size directive is required " |
| "for checking inconsistency on interrupt handler\n"); |
| fprintf (asm_out_file, "\t.vec_size\t%d\n", nds32_isr_vector_size); |
| |
| fprintf (asm_out_file, "\t! ------------------------------------\n"); |
| |
| if (TARGET_ISA_V2) |
| fprintf (asm_out_file, "\t! ISA family\t\t: %s\n", "V2"); |
| if (TARGET_ISA_V3) |
| fprintf (asm_out_file, "\t! ISA family\t\t: %s\n", "V3"); |
| if (TARGET_ISA_V3M) |
| fprintf (asm_out_file, "\t! ISA family\t\t: %s\n", "V3M"); |
| |
| if (TARGET_CMODEL_SMALL) |
| fprintf (asm_out_file, "\t! Code model\t\t: %s\n", "SMALL"); |
| if (TARGET_CMODEL_MEDIUM) |
| fprintf (asm_out_file, "\t! Code model\t\t: %s\n", "MEDIUM"); |
| if (TARGET_CMODEL_LARGE) |
| fprintf (asm_out_file, "\t! Code model\t\t: %s\n", "LARGE"); |
| |
| fprintf (asm_out_file, "\t! Endian setting\t: %s\n", |
| ((TARGET_BIG_ENDIAN) ? "big-endian" |
| : "little-endian")); |
| fprintf (asm_out_file, "\t! Use SP floating-point instruction\t: %s\n", |
| ((TARGET_FPU_SINGLE) ? "Yes" |
| : "No")); |
| fprintf (asm_out_file, "\t! Use DP floating-point instruction\t: %s\n", |
| ((TARGET_FPU_DOUBLE) ? "Yes" |
| : "No")); |
| fprintf (asm_out_file, "\t! ABI version\t\t: %s\n", |
| ((TARGET_HARD_FLOAT) ? "ABI2FP+" |
| : "ABI2")); |
| |
| fprintf (asm_out_file, "\t! ------------------------------------\n"); |
| |
| fprintf (asm_out_file, "\t! Use conditional move\t\t: %s\n", |
| ((TARGET_CMOV) ? "Yes" |
| : "No")); |
| fprintf (asm_out_file, "\t! Use performance extension\t: %s\n", |
| ((TARGET_EXT_PERF) ? "Yes" |
| : "No")); |
| fprintf (asm_out_file, "\t! Use performance extension 2\t: %s\n", |
| ((TARGET_EXT_PERF2) ? "Yes" |
| : "No")); |
| fprintf (asm_out_file, "\t! Use string extension\t\t: %s\n", |
| ((TARGET_EXT_STRING) ? "Yes" |
| : "No")); |
| |
| fprintf (asm_out_file, "\t! ------------------------------------\n"); |
| |
| fprintf (asm_out_file, "\t! V3PUSH instructions\t: %s\n", |
| ((TARGET_V3PUSH) ? "Yes" |
| : "No")); |
| fprintf (asm_out_file, "\t! 16-bit instructions\t: %s\n", |
| ((TARGET_16_BIT) ? "Yes" |
| : "No")); |
| fprintf (asm_out_file, "\t! Reduced registers set\t: %s\n", |
| ((TARGET_REDUCED_REGS) ? "Yes" |
| : "No")); |
| |
| fprintf (asm_out_file, "\t! Support unaligned access\t\t: %s\n", |
| (flag_unaligned_access ? "Yes" |
| : "No")); |
| |
| fprintf (asm_out_file, "\t! ------------------------------------\n"); |
| |
| if (optimize_size) |
| fprintf (asm_out_file, "\t! Optimization level\t: -Os\n"); |
| else if (optimize_fast) |
| fprintf (asm_out_file, "\t! Optimization level\t: -Ofast\n"); |
| else if (optimize_debug) |
| fprintf (asm_out_file, "\t! Optimization level\t: -Og\n"); |
| else |
| fprintf (asm_out_file, "\t! Optimization level\t: -O%d\n", optimize); |
| |
| fprintf (asm_out_file, "\t! ------------------------------------\n"); |
| |
| fprintf (asm_out_file, "\t! Cache block size\t: %d\n", |
| nds32_cache_block_size); |
| |
| fprintf (asm_out_file, "\t! ------------------------------------\n"); |
| |
| nds32_asm_file_start_for_isr (); |
| } |
| |
| static void |
| nds32_asm_file_end (void) |
| { |
| nds32_asm_file_end_for_isr (); |
| |
| fprintf (asm_out_file, "\t! ------------------------------------\n"); |
| } |
| |
| /* -- Output and Generation of Labels. */ |
| |
| static void |
| nds32_asm_globalize_label (FILE *stream, const char *name) |
| { |
| fputs ("\t.global\t", stream); |
| assemble_name (stream, name); |
| fputs ("\n", stream); |
| } |
| |
| /* -- Output of Assembler Instructions. */ |
| |
| static void |
| nds32_print_operand (FILE *stream, rtx x, int code) |
| { |
| HOST_WIDE_INT op_value = 0; |
| HOST_WIDE_INT one_position; |
| HOST_WIDE_INT zero_position; |
| bool pick_lsb_p = false; |
| bool pick_msb_p = false; |
| int regno; |
| |
| if (CONST_INT_P (x)) |
| op_value = INTVAL (x); |
| |
| switch (code) |
| { |
| case 0 : |
| /* Do nothing special. */ |
| break; |
| |
| case 'b': |
| /* Use exact_log2() to search the 0-bit position. */ |
| gcc_assert (CONST_INT_P (x)); |
| zero_position = exact_log2 (~UINTVAL (x) & GET_MODE_MASK (SImode)); |
| gcc_assert (zero_position != -1); |
| fprintf (stream, HOST_WIDE_INT_PRINT_DEC, zero_position); |
| |
| /* No need to handle following process, so return immediately. */ |
| return; |
| |
| case 'e': |
| gcc_assert (MEM_P (x) |
| && GET_CODE (XEXP (x, 0)) == PLUS |
| && GET_CODE (XEXP (XEXP (x, 0), 1)) == CONST_INT); |
| fprintf (stream, HOST_WIDE_INT_PRINT_DEC, INTVAL (XEXP (XEXP (x, 0), 1))); |
| |
| /* No need to handle following process, so return immediately. */ |
| return; |
| case 'B': |
| /* Use exact_log2() to search the 1-bit position. */ |
| gcc_assert (CONST_INT_P (x)); |
| one_position = exact_log2 (UINTVAL (x) & GET_MODE_MASK (SImode)); |
| gcc_assert (one_position != -1); |
| fprintf (stream, HOST_WIDE_INT_PRINT_DEC, one_position); |
| |
| /* No need to handle following process, so return immediately. */ |
| return; |
| |
| case 'L': |
| /* X is supposed to be REG rtx. */ |
| gcc_assert (REG_P (x)); |
| /* Claim that we are going to pick LSB part of X. */ |
| pick_lsb_p = true; |
| break; |
| |
| case 'H': |
| /* X is supposed to be REG rtx. */ |
| gcc_assert (REG_P (x)); |
| /* Claim that we are going to pick MSB part of X. */ |
| pick_msb_p = true; |
| break; |
| |
| case 'V': |
| /* 'x' is supposed to be CONST_INT, get the value. */ |
| gcc_assert (CONST_INT_P (x)); |
| |
| /* According to the Andes architecture, |
| the system/user register index range is 0 ~ 1023. |
| In order to avoid conflict between user-specified-integer value |
| and enum-specified-register value, |
| the 'enum nds32_intrinsic_registers' value |
| in nds32_intrinsic.h starts from 1024. */ |
| if (op_value < 1024 && op_value >= 0) |
| { |
| /* If user gives integer value directly (0~1023), |
| we just print out the value. */ |
| fprintf (stream, HOST_WIDE_INT_PRINT_DEC, op_value); |
| } |
| else if (op_value < 0 |
| || op_value >= ((int) ARRAY_SIZE (nds32_intrinsic_register_names) |
| + 1024)) |
| { |
| /* The enum index value for array size is out of range. */ |
| error ("intrinsic register index is out of range"); |
| } |
| else |
| { |
| /* If user applies normal way with __NDS32_REG_XXX__ enum data, |
| we can print out register name. Remember to substract 1024. */ |
| fprintf (stream, "%s", |
| nds32_intrinsic_register_names[op_value - 1024]); |
| } |
| |
| /* No need to handle following process, so return immediately. */ |
| return; |
| |
| case 'R': /* cctl valck */ |
| /* Note the cctl divide to 5 group and share the same name table. */ |
| if (op_value < 0 || op_value > 4) |
| error ("CCTL intrinsic function subtype out of range!"); |
| fprintf (stream, "%s", nds32_cctl_names[op_value]); |
| return; |
| |
| case 'T': /* cctl idxwbinv */ |
| /* Note the cctl divide to 5 group and share the same name table. */ |
| if (op_value < 0 || op_value > 4) |
| error ("CCTL intrinsic function subtype out of range!"); |
| fprintf (stream, "%s", nds32_cctl_names[op_value + 4]); |
| return; |
| |
| case 'U': /* cctl vawbinv */ |
| /* Note the cctl divide to 5 group and share the same name table. */ |
| if (op_value < 0 || op_value > 4) |
| error ("CCTL intrinsic function subtype out of range!"); |
| fprintf (stream, "%s", nds32_cctl_names[op_value + 8]); |
| return; |
| |
| case 'X': /* cctl idxread */ |
| /* Note the cctl divide to 5 group and share the same name table. */ |
| if (op_value < 0 || op_value > 4) |
| error ("CCTL intrinsic function subtype out of range!"); |
| fprintf (stream, "%s", nds32_cctl_names[op_value + 12]); |
| return; |
| |
| case 'W': /* cctl idxwitre */ |
| /* Note the cctl divide to 5 group and share the same name table. */ |
| if (op_value < 0 || op_value > 4) |
| error ("CCTL intrinsic function subtype out of range!"); |
| fprintf (stream, "%s", nds32_cctl_names[op_value + 16]); |
| return; |
| |
| case 'Z': /* dpref */ |
| fprintf (stream, "%s", nds32_dpref_names[op_value]); |
| return; |
| |
| default : |
| /* Unknown flag. */ |
| output_operand_lossage ("invalid operand output code"); |
| break; |
| } |
| |
| switch (GET_CODE (x)) |
| { |
| case LABEL_REF: |
| case SYMBOL_REF: |
| output_addr_const (stream, x); |
| break; |
| |
| case REG: |
| /* Print a Double-precision register name. */ |
| if ((GET_MODE (x) == DImode || GET_MODE (x) == DFmode) |
| && NDS32_IS_FPR_REGNUM (REGNO (x))) |
| { |
| regno = REGNO (x); |
| if (!NDS32_FPR_REGNO_OK_FOR_DOUBLE (regno)) |
| { |
| output_operand_lossage ("invalid operand for code '%c'", code); |
| break; |
| } |
| fprintf (stream, "$fd%d", (regno - NDS32_FIRST_FPR_REGNUM) >> 1); |
| break; |
| } |
| |
| /* Print LSB or MSB part of register pair if the |
| constraint modifier 'L' or 'H' is specified. */ |
| if ((GET_MODE (x) == DImode || GET_MODE (x) == DFmode) |
| && NDS32_IS_GPR_REGNUM (REGNO (x))) |
| { |
| if ((pick_lsb_p && WORDS_BIG_ENDIAN) |
| || (pick_msb_p && !WORDS_BIG_ENDIAN)) |
| { |
| /* If we would like to print out LSB register under big-endian, |
| or print out MSB register under little-endian, we need to |
| increase register number. */ |
| regno = REGNO (x); |
| regno++; |
| fputs (reg_names[regno], stream); |
| break; |
| } |
| } |
| |
| /* Forbid using static chain register ($r16) |
| on reduced-set registers configuration. */ |
| if (TARGET_REDUCED_REGS |
| && REGNO (x) == STATIC_CHAIN_REGNUM) |
| sorry ("a nested function is not supported for reduced registers"); |
| |
| /* Normal cases, print out register name. */ |
| fputs (reg_names[REGNO (x)], stream); |
| break; |
| |
| case MEM: |
| output_address (GET_MODE (x), XEXP (x, 0)); |
| break; |
| |
| case HIGH: |
| if (GET_CODE (XEXP (x, 0)) == CONST_DOUBLE) |
| { |
| const REAL_VALUE_TYPE *rv; |
| long val; |
| gcc_assert (GET_MODE (x) == SFmode); |
| |
| rv = CONST_DOUBLE_REAL_VALUE (XEXP (x, 0)); |
| REAL_VALUE_TO_TARGET_SINGLE (*rv, val); |
| |
| fprintf (stream, "hi20(0x%lx)", val); |
| } |
| else |
| gcc_unreachable (); |
| break; |
| |
| case CONST_DOUBLE: |
| const REAL_VALUE_TYPE *rv; |
| long val; |
| gcc_assert (GET_MODE (x) == SFmode); |
| |
| rv = CONST_DOUBLE_REAL_VALUE (x); |
| REAL_VALUE_TO_TARGET_SINGLE (*rv, val); |
| |
| fprintf (stream, "0x%lx", val); |
| break; |
| |
| case CODE_LABEL: |
| case CONST_INT: |
| case CONST: |
| output_addr_const (stream, x); |
| break; |
| |
| default: |
| /* Generally, output_addr_const () is able to handle most cases. |
| We want to see what CODE could appear, |
| so we use gcc_unreachable() to stop it. */ |
| debug_rtx (x); |
| gcc_unreachable (); |
| break; |
| } |
| } |
| |
| static void |
| nds32_print_operand_address (FILE *stream, machine_mode /*mode*/, rtx x) |
| { |
| rtx op0, op1; |
| |
| switch (GET_CODE (x)) |
| { |
| case SYMBOL_REF: |
| case CONST: |
| /* [ + symbol_ref] */ |
| /* [ + const_addr], where const_addr = symbol_ref + const_int */ |
| fputs ("[ + ", stream); |
| output_addr_const (stream, x); |
| fputs ("]", stream); |
| break; |
| |
| case REG: |
| /* Forbid using static chain register ($r16) |
| on reduced-set registers configuration. */ |
| if (TARGET_REDUCED_REGS |
| && REGNO (x) == STATIC_CHAIN_REGNUM) |
| sorry ("a nested function is not supported for reduced registers"); |
| |
| /* [Ra] */ |
| fprintf (stream, "[%s]", reg_names[REGNO (x)]); |
| break; |
| |
| case PLUS: |
| op0 = XEXP (x, 0); |
| op1 = XEXP (x, 1); |
| |
| /* Checking op0, forbid using static chain register ($r16) |
| on reduced-set registers configuration. */ |
| if (TARGET_REDUCED_REGS |
| && REG_P (op0) |
| && REGNO (op0) == STATIC_CHAIN_REGNUM) |
| sorry ("a nested function is not supported for reduced registers"); |
| /* Checking op1, forbid using static chain register ($r16) |
| on reduced-set registers configuration. */ |
| if (TARGET_REDUCED_REGS |
| && REG_P (op1) |
| && REGNO (op1) == STATIC_CHAIN_REGNUM) |
| sorry ("a nested function is not supported for reduced registers"); |
| |
| if (REG_P (op0) && CONST_INT_P (op1)) |
| { |
| /* [Ra + imm] */ |
| fprintf (stream, "[%s + (" HOST_WIDE_INT_PRINT_DEC ")]", |
| reg_names[REGNO (op0)], INTVAL (op1)); |
| } |
| else if (REG_P (op0) && REG_P (op1)) |
| { |
| /* [Ra + Rb] */ |
| fprintf (stream, "[%s + %s]", |
| reg_names[REGNO (op0)], reg_names[REGNO (op1)]); |
| } |
| else if (GET_CODE (op0) == MULT && REG_P (op1)) |
| { |
| /* [Ra + Rb << sv] |
| From observation, the pattern looks like: |
| (plus:SI (mult:SI (reg:SI 58) |
| (const_int 4 [0x4])) |
| (reg/f:SI 57)) */ |
| int sv; |
| |
| /* We need to set sv to output shift value. */ |
| if (INTVAL (XEXP (op0, 1)) == 1) |
| sv = 0; |
| else if (INTVAL (XEXP (op0, 1)) == 2) |
| sv = 1; |
| else if (INTVAL (XEXP (op0, 1)) == 4) |
| sv = 2; |
| else if (INTVAL (XEXP (op0, 1)) == 8) |
| sv = 3; |
| else |
| gcc_unreachable (); |
| |
| fprintf (stream, "[%s + %s << %d]", |
| reg_names[REGNO (op1)], |
| reg_names[REGNO (XEXP (op0, 0))], |
| sv); |
| } |
| else |
| { |
| /* The control flow is not supposed to be here. */ |
| debug_rtx (x); |
| gcc_unreachable (); |
| } |
| |
| break; |
| |
| case POST_MODIFY: |
| /* (post_modify (regA) (plus (regA) (regB))) |
| (post_modify (regA) (plus (regA) (const_int))) |
| We would like to extract |
| regA and regB (or const_int) from plus rtx. */ |
| op0 = XEXP (XEXP (x, 1), 0); |
| op1 = XEXP (XEXP (x, 1), 1); |
| |
| /* Checking op0, forbid using static chain register ($r16) |
| on reduced-set registers configuration. */ |
| if (TARGET_REDUCED_REGS |
| && REG_P (op0) |
| && REGNO (op0) == STATIC_CHAIN_REGNUM) |
| sorry ("a nested function is not supported for reduced registers"); |
| /* Checking op1, forbid using static chain register ($r16) |
| on reduced-set registers configuration. */ |
| if (TARGET_REDUCED_REGS |
| && REG_P (op1) |
| && REGNO (op1) == STATIC_CHAIN_REGNUM) |
| sorry ("a nested function is not supported for reduced registers"); |
| |
| if (REG_P (op0) && REG_P (op1)) |
| { |
| /* [Ra], Rb */ |
| fprintf (stream, "[%s], %s", |
| reg_names[REGNO (op0)], reg_names[REGNO (op1)]); |
| } |
| else if (REG_P (op0) && CONST_INT_P (op1)) |
| { |
| /* [Ra], imm */ |
| fprintf (stream, "[%s], " HOST_WIDE_INT_PRINT_DEC, |
| reg_names[REGNO (op0)], INTVAL (op1)); |
| } |
| else |
| { |
| /* The control flow is not supposed to be here. */ |
| debug_rtx (x); |
| gcc_unreachable (); |
| } |
| |
| break; |
| |
| case POST_INC: |
| case POST_DEC: |
| op0 = XEXP (x, 0); |
| |
| /* Checking op0, forbid using static chain register ($r16) |
| on reduced-set registers configuration. */ |
| if (TARGET_REDUCED_REGS |
| && REG_P (op0) |
| && REGNO (op0) == STATIC_CHAIN_REGNUM) |
| sorry ("a nested function is not supported for reduced registers");
|