| /* Subroutines used for code generation of Andes NDS32 cpu for GNU compiler |
| Copyright (C) 2012-2014 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/>. */ |
| |
| |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "tm.h" |
| #include "tree.h" |
| #include "stor-layout.h" |
| #include "varasm.h" |
| #include "calls.h" |
| #include "rtl.h" |
| #include "regs.h" |
| #include "hard-reg-set.h" |
| #include "insn-config.h" /* Required by recog.h. */ |
| #include "conditions.h" |
| #include "output.h" |
| #include "insn-attr.h" /* For DFA state_t. */ |
| #include "insn-codes.h" /* For CODE_FOR_xxx. */ |
| #include "reload.h" /* For push_reload(). */ |
| #include "flags.h" |
| #include "function.h" |
| #include "expr.h" |
| #include "recog.h" |
| #include "diagnostic-core.h" |
| #include "df.h" |
| #include "tm_p.h" |
| #include "tm-constrs.h" |
| #include "optabs.h" /* For GEN_FCN. */ |
| #include "target.h" |
| #include "target-def.h" |
| #include "langhooks.h" /* For add_builtin_function(). */ |
| #include "ggc.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. */ |
| |
| /* Refer to nds32.h, there are maximum 73 isr vectors in nds32 architecture. |
| 0 for reset handler with __attribute__((reset())), |
| 1-8 for exception handler with __attribute__((exception(1,...,8))), |
| and 9-72 for interrupt handler with __attribute__((interrupt(0,...,63))). |
| We use an array to record essential information for each vector. */ |
| static struct nds32_isr_info nds32_isr_vectors[NDS32_N_ISR_VECTORS]; |
| |
| /* 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[] = |
| { |
| "$PSW", "$IPSW", "$ITYPE", "$IPC" |
| }; |
| |
| /* 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, handler, affects_type_identity } */ |
| |
| /* The interrupt vid: [0-63]+ (actual vector number starts from 9 to 72). */ |
| { "interrupt", 1, 64, false, false, false, NULL, false }, |
| /* The exception vid: [1-8]+ (actual vector number starts from 1 to 8). */ |
| { "exception", 1, 8, false, false, false, NULL, false }, |
| /* Argument is user's interrupt numbers. The vector number is always 0. */ |
| { "reset", 1, 1, false, false, false, NULL, false }, |
| |
| /* The attributes describing isr nested type. */ |
| { "nested", 0, 0, false, false, false, NULL, false }, |
| { "not_nested", 0, 0, false, false, false, NULL, false }, |
| { "nested_ready", 0, 0, false, false, false, NULL, false }, |
| |
| /* The attributes describing isr register save scheme. */ |
| { "save_all", 0, 0, false, false, false, NULL, false }, |
| { "partial_save", 0, 0, false, false, false, NULL, false }, |
| |
| /* The attributes used by reset attribute. */ |
| { "nmi", 1, 1, false, false, false, NULL, false }, |
| { "warm", 1, 1, false, false, false, NULL, false }, |
| |
| /* The attribute telling no prologue/epilogue. */ |
| { "naked", 0, 0, false, false, false, NULL, false }, |
| |
| /* The last attribute spec is set to be NULL. */ |
| { NULL, 0, 0, false, false, false, NULL, false } |
| }; |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| |
| /* 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_alloc_cleared_machine_function (); |
| |
| /* 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; |
| |
| 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; |
| |
| /* 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; |
| |
| /* Get variadic arguments size to prepare pretend arguments and |
| push them into stack at prologue. |
| Currently, we do not push variadic arguments by ourself. |
| We have GCC handle all the works. |
| The caller will push all corresponding nameless arguments into stack, |
| and the callee is able to retrieve them without problems. |
| These variables are still preserved in case one day |
| we would like caller passing arguments with registers. */ |
| cfun->machine->va_args_size = 0; |
| cfun->machine->va_args_first_regno = SP_REGNUM; |
| cfun->machine->va_args_last_regno = SP_REGNUM; |
| |
| /* 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 = (df_regs_ever_live_p (LP_REGNUM)) ? 4 : 0; |
| |
| /* Initially there is no padding bytes. */ |
| cfun->machine->callee_saved_area_padding_bytes = 0; |
| |
| /* Calculate the bytes of saving callee-saved registers on stack. */ |
| cfun->machine->callee_saved_regs_size = 0; |
| cfun->machine->callee_saved_regs_first_regno = SP_REGNUM; |
| cfun->machine->callee_saved_regs_last_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_regs_first_regno == SP_REGNUM) |
| cfun->machine->callee_saved_regs_first_regno = r; |
| /* Mark the last required callee-saved register. */ |
| cfun->machine->callee_saved_regs_last_regno = r; |
| } |
| } |
| |
| /* 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. |
| 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_regs_first_regno == SP_REGNUM |
| && cfun->machine->callee_saved_regs_last_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. */ |
| 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; |
| } |
| |
| /* 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. */ |
| if (TARGET_V3PUSH) |
| { |
| /* Recompute: |
| cfun->machine->fp_size |
| cfun->machine->gp_size |
| cfun->machine->lp_size |
| cfun->machine->callee_saved_regs_first_regno |
| cfun->machine->callee_saved_regs_last_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_regs_first_regno = 6; |
| |
| if (cfun->machine->callee_saved_regs_last_regno <= 6) |
| { |
| /* Re = $r6 */ |
| cfun->machine->callee_saved_regs_last_regno = 6; |
| } |
| else if (cfun->machine->callee_saved_regs_last_regno <= 8) |
| { |
| /* Re = $r8 */ |
| cfun->machine->callee_saved_regs_last_regno = 8; |
| } |
| else if (cfun->machine->callee_saved_regs_last_regno <= 10) |
| { |
| /* Re = $r10 */ |
| cfun->machine->callee_saved_regs_last_regno = 10; |
| } |
| else if (cfun->machine->callee_saved_regs_last_regno <= 14) |
| { |
| /* Re = $r14 */ |
| cfun->machine->callee_saved_regs_last_regno = 14; |
| } |
| else if (cfun->machine->callee_saved_regs_last_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_regs_last_regno = 6; |
| } |
| else |
| { |
| /* The program flow should not go here. */ |
| gcc_unreachable (); |
| } |
| } |
| |
| /* We have correctly set callee_saved_regs_first_regno |
| and callee_saved_regs_last_regno. |
| Initially, the callee_saved_regs_size is supposed to be 0. |
| As long as callee_saved_regs_last_regno is not SP_REGNUM, |
| we can update callee_saved_regs_size with new size. */ |
| if (cfun->machine->callee_saved_regs_last_regno != SP_REGNUM) |
| { |
| /* Compute pushed size of callee-saved registers. */ |
| cfun->machine->callee_saved_regs_size |
| = 4 * (cfun->machine->callee_saved_regs_last_regno |
| - cfun->machine->callee_saved_regs_first_regno |
| + 1); |
| } |
| |
| /* Important: We need to make sure that |
| (va_args_size + fp_size + gp_size |
| + lp_size + callee_saved_regs_size) |
| is 8-byte alignment. |
| If it is not, calculate the padding bytes. */ |
| block_size = cfun->machine->va_args_size |
| + cfun->machine->fp_size |
| + cfun->machine->gp_size |
| + cfun->machine->lp_size |
| + cfun->machine->callee_saved_regs_size; |
| if (!NDS32_DOUBLE_WORD_ALIGN_P (block_size)) |
| { |
| cfun->machine->callee_saved_area_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 rtx |
| nds32_gen_stack_push_multiple (rtx Rb, rtx Re, |
| rtx En4 ATTRIBUTE_UNUSED) |
| { |
| int 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; |
| |
| /* 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 (cfun->machine->fp_size) |
| extra_count++; |
| if (cfun->machine->gp_size) |
| extra_count++; |
| if (cfun->machine->lp_size) |
| extra_count++; |
| /* Note that Rb and Re may be SP_REGNUM. DO NOT count it in. */ |
| if (REGNO (Rb) == SP_REGNUM && REGNO (Re) == SP_REGNUM) |
| num_use_regs = extra_count; |
| else |
| num_use_regs = REGNO (Re) - REGNO (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 = REGNO (Rb); regno <= (int) 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 (VOIDmode, 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 (cfun->machine->fp_size) |
| { |
| reg = gen_rtx_REG (SImode, FP_REGNUM); |
| mem = gen_frame_mem (SImode, plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset)); |
| push_rtx = gen_rtx_SET (VOIDmode, mem, reg); |
| XVECEXP (parallel_insn, 0, par_index) = push_rtx; |
| RTX_FRAME_RELATED_P (push_rtx) = 1; |
| offset = offset + 4; |
| par_index++; |
| } |
| if (cfun->machine->gp_size) |
| { |
| reg = gen_rtx_REG (SImode, GP_REGNUM); |
| mem = gen_frame_mem (SImode, plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset)); |
| push_rtx = gen_rtx_SET (VOIDmode, mem, reg); |
| XVECEXP (parallel_insn, 0, par_index) = push_rtx; |
| RTX_FRAME_RELATED_P (push_rtx) = 1; |
| offset = offset + 4; |
| par_index++; |
| } |
| if (cfun->machine->lp_size) |
| { |
| reg = gen_rtx_REG (SImode, LP_REGNUM); |
| mem = gen_frame_mem (SImode, plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset)); |
| push_rtx = gen_rtx_SET (VOIDmode, 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 (VOIDmode, |
| 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; |
| |
| return parallel_insn; |
| } |
| |
| /* 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 rtx |
| nds32_gen_stack_pop_multiple (rtx Rb, rtx Re, |
| rtx En4 ATTRIBUTE_UNUSED) |
| { |
| int 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; |
| |
| /* 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 (cfun->machine->fp_size) |
| extra_count++; |
| if (cfun->machine->gp_size) |
| extra_count++; |
| if (cfun->machine->lp_size) |
| extra_count++; |
| /* Note that Rb and Re may be SP_REGNUM. DO NOT count it in. */ |
| if (REGNO (Rb) == SP_REGNUM && REGNO (Re) == SP_REGNUM) |
| num_use_regs = extra_count; |
| else |
| num_use_regs = REGNO (Re) - REGNO (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 = REGNO (Rb); regno <= (int) 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 (VOIDmode, reg, mem); |
| XVECEXP (parallel_insn, 0, par_index) = pop_rtx; |
| RTX_FRAME_RELATED_P (pop_rtx) = 1; |
| offset = offset + 4; |
| par_index++; |
| } |
| |
| /* Create (set fp mem), (set gp mem), and (set lp mem) if necessary. */ |
| if (cfun->machine->fp_size) |
| { |
| reg = gen_rtx_REG (SImode, FP_REGNUM); |
| mem = gen_frame_mem (SImode, plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset)); |
| pop_rtx = gen_rtx_SET (VOIDmode, reg, mem); |
| XVECEXP (parallel_insn, 0, par_index) = pop_rtx; |
| RTX_FRAME_RELATED_P (pop_rtx) = 1; |
| offset = offset + 4; |
| par_index++; |
| } |
| if (cfun->machine->gp_size) |
| { |
| reg = gen_rtx_REG (SImode, GP_REGNUM); |
| mem = gen_frame_mem (SImode, plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset)); |
| pop_rtx = gen_rtx_SET (VOIDmode, reg, mem); |
| XVECEXP (parallel_insn, 0, par_index) = pop_rtx; |
| RTX_FRAME_RELATED_P (pop_rtx) = 1; |
| offset = offset + 4; |
| par_index++; |
| } |
| if (cfun->machine->lp_size) |
| { |
| reg = gen_rtx_REG (SImode, LP_REGNUM); |
| mem = gen_frame_mem (SImode, plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset)); |
| pop_rtx = gen_rtx_SET (VOIDmode, reg, mem); |
| XVECEXP (parallel_insn, 0, par_index) = pop_rtx; |
| RTX_FRAME_RELATED_P (pop_rtx) = 1; |
| offset = offset + 4; |
| par_index++; |
| } |
| |
| /* Create (set sp sp+x). */ |
| |
| /* The offset value is already in place. No need to re-calculate it. */ |
| adjust_sp_rtx |
| = gen_rtx_SET (VOIDmode, |
| 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; |
| |
| return parallel_insn; |
| } |
| |
| /* Function to create a parallel rtx pattern |
| which presents stack v3push behavior. |
| The overall concept are: |
| "push registers to memory", |
| "adjust stack pointer". */ |
| static rtx |
| nds32_gen_stack_v3push (rtx Rb, |
| rtx Re, |
| rtx En4 ATTRIBUTE_UNUSED, |
| rtx imm8u) |
| { |
| int 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 = REGNO (Re) - REGNO (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 = REGNO (Rb); regno <= (int) 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 (VOIDmode, 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 (VOIDmode, 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 (VOIDmode, 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 (VOIDmode, 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 (VOIDmode, |
| stack_pointer_rtx, |
| plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset - INTVAL (imm8u))); |
| XVECEXP (parallel_insn, 0, par_index) = adjust_sp_rtx; |
| RTX_FRAME_RELATED_P (adjust_sp_rtx) = 1; |
| |
| return parallel_insn; |
| } |
| |
| /* Function to create a parallel rtx pattern |
| which presents stack v3pop behavior. |
| The overall concept are: |
| "pop registers from memory", |
| "adjust stack pointer". */ |
| static rtx |
| nds32_gen_stack_v3pop (rtx Rb, |
| rtx Re, |
| rtx En4 ATTRIBUTE_UNUSED, |
| rtx imm8u) |
| { |
| int regno; |
| int num_use_regs; |
| int par_index; |
| int offset; |
| |
| rtx reg; |
| rtx mem; |
| rtx pop_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 (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 = REGNO (Re) - REGNO (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 = REGNO (Rb); regno <= (int) 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 (VOIDmode, reg, mem); |
| XVECEXP (parallel_insn, 0, par_index) = pop_rtx; |
| RTX_FRAME_RELATED_P (pop_rtx) = 1; |
| offset = offset + 4; |
| par_index++; |
| } |
| |
| /* 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 (VOIDmode, reg, mem); |
| XVECEXP (parallel_insn, 0, par_index) = pop_rtx; |
| RTX_FRAME_RELATED_P (pop_rtx) = 1; |
| offset = offset + 4; |
| par_index++; |
| /* 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 (VOIDmode, reg, mem); |
| XVECEXP (parallel_insn, 0, par_index) = pop_rtx; |
| RTX_FRAME_RELATED_P (pop_rtx) = 1; |
| offset = offset + 4; |
| par_index++; |
| /* 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 (VOIDmode, reg, mem); |
| XVECEXP (parallel_insn, 0, par_index) = pop_rtx; |
| RTX_FRAME_RELATED_P (pop_rtx) = 1; |
| offset = offset + 4; |
| par_index++; |
| |
| /* 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 (VOIDmode, |
| stack_pointer_rtx, |
| plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset + INTVAL (imm8u))); |
| XVECEXP (parallel_insn, 0, par_index) = adjust_sp_rtx; |
| RTX_FRAME_RELATED_P (adjust_sp_rtx) = 1; |
| |
| return parallel_insn; |
| } |
| |
| /* A subroutine that checks multiple load and store |
| using consecutive registers. |
| OP is a parallel rtx we would like to check. |
| LOAD_P indicates whether we are checking load operation. |
| PAR_INDEX is starting element of parallel rtx. |
| FIRST_ELT_REGNO is used to tell starting register number. |
| COUNT helps us to check consecutive register numbers. */ |
| static bool |
| nds32_consecutive_registers_load_store_p (rtx op, |
| bool load_p, |
| int par_index, |
| int first_elt_regno, |
| int count) |
| { |
| int i; |
| int check_regno; |
| rtx elt; |
| rtx elt_reg; |
| rtx elt_mem; |
| |
| for (i = 0; i < count; i++) |
| { |
| /* Pick up each element from parallel rtx. */ |
| elt = XVECEXP (op, 0, i + par_index); |
| |
| /* If this element is not a 'set' rtx, return false immediately. */ |
| if (GET_CODE (elt) != SET) |
| return false; |
| |
| /* Pick up reg and mem of this element. */ |
| elt_reg = load_p ? SET_DEST (elt) : SET_SRC (elt); |
| elt_mem = load_p ? SET_SRC (elt) : SET_DEST (elt); |
| |
| /* If elt_reg is not a expected reg rtx, return false. */ |
| if (GET_CODE (elt_reg) != REG || GET_MODE (elt_reg) != SImode) |
| return false; |
| /* If elt_mem is not a expected mem rtx, return false. */ |
| if (GET_CODE (elt_mem) != MEM || GET_MODE (elt_mem) != SImode) |
| return false; |
| |
| /* The consecutive registers should be in (Rb,Rb+1...Re) order. */ |
| check_regno = first_elt_regno + i; |
| |
| /* If the register number is not continuous, return false. */ |
| if (REGNO (elt_reg) != (unsigned int) check_regno) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* A helper function to emit section head template. */ |
| static void |
| nds32_emit_section_head_template (char section_name[], |
| char symbol_name[], |
| int align_value, |
| bool object_p) |
| { |
| const char *flags_str; |
| const char *type_str; |
| |
| flags_str = (object_p) ? "\"a\"" : "\"ax\""; |
| type_str = (object_p) ? "@object" : "@function"; |
| |
| fprintf (asm_out_file, "\t.section\t%s, %s\n", section_name, flags_str); |
| fprintf (asm_out_file, "\t.align\t%d\n", align_value); |
| fprintf (asm_out_file, "\t.global\t%s\n", symbol_name); |
| fprintf (asm_out_file, "\t.type\t%s, %s\n", symbol_name, type_str); |
| fprintf (asm_out_file, "%s:\n", symbol_name); |
| } |
| |
| /* A helper function to emit section tail template. */ |
| static void |
| nds32_emit_section_tail_template (char symbol_name[]) |
| { |
| fprintf (asm_out_file, "\t.size\t%s, .-%s\n", symbol_name, symbol_name); |
| } |
| |
| /* Function to emit isr jump table section. */ |
| static void |
| nds32_emit_isr_jmptbl_section (int vector_id) |
| { |
| char section_name[100]; |
| char symbol_name[100]; |
| |
| /* Prepare jmptbl section and symbol name. */ |
| snprintf (section_name, sizeof (section_name), |
| ".nds32_jmptbl.%02d", vector_id); |
| snprintf (symbol_name, sizeof (symbol_name), |
| "_nds32_jmptbl_%02d", vector_id); |
| |
| nds32_emit_section_head_template (section_name, symbol_name, 2, true); |
| fprintf (asm_out_file, "\t.word\t%s\n", |
| nds32_isr_vectors[vector_id].func_name); |
| nds32_emit_section_tail_template (symbol_name); |
| } |
| |
| /* Function to emit isr vector section. */ |
| static void |
| nds32_emit_isr_vector_section (int vector_id) |
| { |
| unsigned int vector_number_offset = 0; |
| const char *c_str = "CATEGORY"; |
| const char *sr_str = "SR"; |
| const char *nt_str = "NT"; |
| const char *vs_str = "VS"; |
| char first_level_handler_name[100]; |
| char section_name[100]; |
| char symbol_name[100]; |
| |
| /* Set the vector number offset so that we can calculate |
| the value that user specifies in the attribute. |
| We also prepare the category string for first level handler name. */ |
| switch (nds32_isr_vectors[vector_id].category) |
| { |
| case NDS32_ISR_INTERRUPT: |
| vector_number_offset = 9; |
| c_str = "i"; |
| break; |
| case NDS32_ISR_EXCEPTION: |
| vector_number_offset = 0; |
| c_str = "e"; |
| break; |
| case NDS32_ISR_NONE: |
| case NDS32_ISR_RESET: |
| /* Normally it should not be here. */ |
| gcc_unreachable (); |
| break; |
| } |
| |
| /* Prepare save reg string for first level handler name. */ |
| switch (nds32_isr_vectors[vector_id].save_reg) |
| { |
| case NDS32_SAVE_ALL: |
| sr_str = "sa"; |
| break; |
| case NDS32_PARTIAL_SAVE: |
| sr_str = "ps"; |
| break; |
| } |
| |
| /* Prepare nested type string for first level handler name. */ |
| switch (nds32_isr_vectors[vector_id].nested_type) |
| { |
| case NDS32_NESTED: |
| nt_str = "ns"; |
| break; |
| case NDS32_NOT_NESTED: |
| nt_str = "nn"; |
| break; |
| case NDS32_NESTED_READY: |
| nt_str = "nr"; |
| break; |
| } |
| |
| /* Currently we have 4-byte or 16-byte size for each vector. |
| If it is 4-byte, the first level handler name has suffix string "_4b". */ |
| vs_str = (nds32_isr_vector_size == 4) ? "_4b" : ""; |
| |
| /* Now we can create first level handler name. */ |
| snprintf (first_level_handler_name, sizeof (first_level_handler_name), |
| "_nds32_%s_%s_%s%s", c_str, sr_str, nt_str, vs_str); |
| |
| /* Prepare vector section and symbol name. */ |
| snprintf (section_name, sizeof (section_name), |
| ".nds32_vector.%02d", vector_id); |
| snprintf (symbol_name, sizeof (symbol_name), |
| "_nds32_vector_%02d%s", vector_id, vs_str); |
| |
| |
| /* Everything is ready. We can start emit vector section content. */ |
| nds32_emit_section_head_template (section_name, symbol_name, |
| floor_log2 (nds32_isr_vector_size), false); |
| |
| /* According to the vector size, the instructions in the |
| vector section may be different. */ |
| if (nds32_isr_vector_size == 4) |
| { |
| /* This block is for 4-byte vector size. |
| Hardware $VID support is necessary and only one instruction |
| is needed in vector section. */ |
| fprintf (asm_out_file, "\tj\t%s ! jump to first level handler\n", |
| first_level_handler_name); |
| } |
| else |
| { |
| /* This block is for 16-byte vector size. |
| There is NO hardware $VID so that we need several instructions |
| such as pushing GPRs and preparing software vid at vector section. |
| For pushing GPRs, there are four variations for |
| 16-byte vector content and we have to handle each combination. |
| For preparing software vid, note that the vid need to |
| be substracted vector_number_offset. */ |
| if (TARGET_REDUCED_REGS) |
| { |
| if (nds32_isr_vectors[vector_id].save_reg == NDS32_SAVE_ALL) |
| { |
| /* Case of reduced set registers and save_all attribute. */ |
| fprintf (asm_out_file, "\t! reduced set regs + save_all\n"); |
| fprintf (asm_out_file, "\tsmw.adm\t$r15, [$sp], $r15, 0xf\n"); |
| fprintf (asm_out_file, "\tsmw.adm\t$r0, [$sp], $r10, 0x0\n"); |
| |
| } |
| else |
| { |
| /* Case of reduced set registers and partial_save attribute. */ |
| fprintf (asm_out_file, "\t! reduced set regs + partial_save\n"); |
| fprintf (asm_out_file, "\tsmw.adm\t$r15, [$sp], $r15, 0x2\n"); |
| fprintf (asm_out_file, "\tsmw.adm\t$r0, [$sp], $r5, 0x0\n"); |
| } |
| } |
| else |
| { |
| if (nds32_isr_vectors[vector_id].save_reg == NDS32_SAVE_ALL) |
| { |
| /* Case of full set registers and save_all attribute. */ |
| fprintf (asm_out_file, "\t! full set regs + save_all\n"); |
| fprintf (asm_out_file, "\tsmw.adm\t$r0, [$sp], $r27, 0xf\n"); |
| } |
| else |
| { |
| /* Case of full set registers and partial_save attribute. */ |
| fprintf (asm_out_file, "\t! full set regs + partial_save\n"); |
| fprintf (asm_out_file, "\tsmw.adm\t$r15, [$sp], $r27, 0x2\n"); |
| fprintf (asm_out_file, "\tsmw.adm\t$r0, [$sp], $r5, 0x0\n"); |
| } |
| } |
| |
| fprintf (asm_out_file, "\tmovi\t$r0, %d ! preparing software vid\n", |
| vector_id - vector_number_offset); |
| fprintf (asm_out_file, "\tj\t%s ! jump to first level handler\n", |
| first_level_handler_name); |
| } |
| |
| nds32_emit_section_tail_template (symbol_name); |
| } |
| |
| /* Function to emit isr reset handler content. |
| Including all jmptbl/vector references, jmptbl section, |
| vector section, nmi handler section, and warm handler section. */ |
| static void |
| nds32_emit_isr_reset_content (void) |
| { |
| unsigned int i; |
| unsigned int total_n_vectors; |
| const char *vs_str; |
| char reset_handler_name[100]; |
| char section_name[100]; |
| char symbol_name[100]; |
| |
| total_n_vectors = nds32_isr_vectors[0].total_n_vectors; |
| vs_str = (nds32_isr_vector_size == 4) ? "_4b" : ""; |
| |
| fprintf (asm_out_file, "\t! RESET HANDLER CONTENT - BEGIN !\n"); |
| |
| /* Create references in .rodata according to total number of vectors. */ |
| fprintf (asm_out_file, "\t.section\t.rodata\n"); |
| fprintf (asm_out_file, "\t.align\t2\n"); |
| |
| /* Emit jmptbl references. */ |
| fprintf (asm_out_file, "\t ! references to jmptbl section entries\n"); |
| for (i = 0; i < total_n_vectors; i++) |
| fprintf (asm_out_file, "\t.word\t_nds32_jmptbl_%02d\n", i); |
| |
| /* Emit vector references. */ |
| fprintf (asm_out_file, "\t ! references to vector section entries\n"); |
| for (i = 0; i < total_n_vectors; i++) |
| fprintf (asm_out_file, "\t.word\t_nds32_vector_%02d%s\n", i, vs_str); |
| |
| /* Emit jmptbl_00 section. */ |
| snprintf (section_name, sizeof (section_name), ".nds32_jmptbl.00"); |
| snprintf (symbol_name, sizeof (symbol_name), "_nds32_jmptbl_00"); |
| |
| fprintf (asm_out_file, "\t! ....................................\n"); |
| nds32_emit_section_head_template (section_name, symbol_name, 2, true); |
| fprintf (asm_out_file, "\t.word\t%s\n", |
| nds32_isr_vectors[0].func_name); |
| nds32_emit_section_tail_template (symbol_name); |
| |
| /* Emit vector_00 section. */ |
| snprintf (section_name, sizeof (section_name), ".nds32_vector.00"); |
| snprintf (symbol_name, sizeof (symbol_name), "_nds32_vector_00%s", vs_str); |
| snprintf (reset_handler_name, sizeof (reset_handler_name), |
| "_nds32_reset%s", vs_str); |
| |
| fprintf (asm_out_file, "\t! ....................................\n"); |
| nds32_emit_section_head_template (section_name, symbol_name, |
| floor_log2 (nds32_isr_vector_size), false); |
| fprintf (asm_out_file, "\tj\t%s ! jump to reset handler\n", |
| reset_handler_name); |
| nds32_emit_section_tail_template (symbol_name); |
| |
| /* Emit nmi handler section. */ |
| snprintf (section_name, sizeof (section_name), ".nds32_nmih"); |
| snprintf (symbol_name, sizeof (symbol_name), "_nds32_nmih"); |
| |
| fprintf (asm_out_file, "\t! ....................................\n"); |
| nds32_emit_section_head_template (section_name, symbol_name, 2, true); |
| fprintf (asm_out_file, "\t.word\t%s\n", |
| (strlen (nds32_isr_vectors[0].nmi_name) == 0) |
| ? "0" |
| : nds32_isr_vectors[0].nmi_name); |
| nds32_emit_section_tail_template (symbol_name); |
| |
| /* Emit warm handler section. */ |
| snprintf (section_name, sizeof (section_name), ".nds32_wrh"); |
| snprintf (symbol_name, sizeof (symbol_name), "_nds32_wrh"); |
| |
| fprintf (asm_out_file, "\t! ....................................\n"); |
| nds32_emit_section_head_template (section_name, symbol_name, 2, true); |
| fprintf (asm_out_file, "\t.word\t%s\n", |
| (strlen (nds32_isr_vectors[0].warm_name) == 0) |
| ? "0" |
| : nds32_isr_vectors[0].warm_name); |
| nds32_emit_section_tail_template (symbol_name); |
| |
| fprintf (asm_out_file, "\t! RESET HANDLER CONTENT - END !\n"); |
| } |
| |
| /* Function for nds32_merge_decl_attributes() and nds32_insert_attributes() |
| to check if there are any conflict isr-specific attributes being set. |
| We need to check: |
| 1. Only 'save_all' or 'partial_save' in the attributes. |
| 2. Only 'nested', 'not_nested', or 'nested_ready' in the attributes. |
| 3. Only 'interrupt', 'exception', or 'reset' in the attributes. */ |
| static void |
| nds32_check_isr_attrs_conflict (tree func_decl, tree func_attrs) |
| { |
| int save_all_p, partial_save_p; |
| int nested_p, not_nested_p, nested_ready_p; |
| int intr_p, excp_p, reset_p; |
| |
| /* Initialize variables. */ |
| save_all_p = partial_save_p = 0; |
| nested_p = not_nested_p = nested_ready_p = 0; |
| intr_p = excp_p = reset_p = 0; |
| |
| /* We must check at MOST one attribute to set save-reg. */ |
| if (lookup_attribute ("save_all", func_attrs)) |
| save_all_p = 1; |
| if (lookup_attribute ("partial_save", func_attrs)) |
| partial_save_p = 1; |
| |
| if ((save_all_p + partial_save_p) > 1) |
| error ("multiple save reg attributes to function %qD", func_decl); |
| |
| /* We must check at MOST one attribute to set nested-type. */ |
| if (lookup_attribute ("nested", func_attrs)) |
| nested_p = 1; |
| if (lookup_attribute ("not_nested", func_attrs)) |
| not_nested_p = 1; |
| if (lookup_attribute ("nested_ready", func_attrs)) |
| nested_ready_p = 1; |
| |
| if ((nested_p + not_nested_p + nested_ready_p) > 1) |
| error ("multiple nested types attributes to function %qD", func_decl); |
| |
| /* We must check at MOST one attribute to |
| set interrupt/exception/reset. */ |
| if (lookup_attribute ("interrupt", func_attrs)) |
| intr_p = 1; |
| if (lookup_attribute ("exception", func_attrs)) |
| excp_p = 1; |
| if (lookup_attribute ("reset", func_attrs)) |
| reset_p = 1; |
| |
| if ((intr_p + excp_p + reset_p) > 1) |
| error ("multiple interrupt attributes to function %qD", func_decl); |
| } |
| |
| /* Function to construct isr vectors information array. |
| We DO NOT HAVE TO check if the attributes are valid |
| because those works are supposed to be done on |
| nds32_merge_decl_attributes() and nds32_insert_attributes(). */ |
| static void |
| nds32_construct_isr_vectors_information (tree func_attrs, |
| const char *func_name) |
| { |
| tree save_all, partial_save; |
| tree nested, not_nested, nested_ready; |
| tree intr, excp, reset; |
| |
| save_all = lookup_attribute ("save_all", func_attrs); |
| partial_save = lookup_attribute ("partial_save", func_attrs); |
| |
| nested = lookup_attribute ("nested", func_attrs); |
| not_nested = lookup_attribute ("not_nested", func_attrs); |
| nested_ready = lookup_attribute ("nested_ready", func_attrs); |
| |
| intr = lookup_attribute ("interrupt", func_attrs); |
| excp = lookup_attribute ("exception", func_attrs); |
| reset = lookup_attribute ("reset", func_attrs); |
| |
| /* If there is no interrupt/exception/reset, we can return immediately. */ |
| if (!intr && !excp && !reset) |
| return; |
| |
| /* If we are here, either we have interrupt/exception, |
| or reset attribute. */ |
| if (intr || excp) |
| { |
| tree id_list; |
| |
| /* Prepare id list so that we can traverse and set vector id. */ |
| id_list = (intr) ? (TREE_VALUE (intr)) : (TREE_VALUE (excp)); |
| |
| while (id_list) |
| { |
| tree id; |
| int vector_id; |
| unsigned int vector_number_offset; |
| |
| /* The way to handle interrupt or exception is the same, |
| we just need to take care of actual vector number. |
| For interrupt(0..63), the actual vector number is (9..72). |
| For exception(1..8), the actual vector number is (1..8). */ |
| vector_number_offset = (intr) ? (9) : (0); |
| |
| /* Pick up each vector id value. */ |
| id = TREE_VALUE (id_list); |
| /* Add vector_number_offset to get actual vector number. */ |
| vector_id = TREE_INT_CST_LOW (id) + vector_number_offset; |
| |
| /* Enable corresponding vector and set function name. */ |
| nds32_isr_vectors[vector_id].category = (intr) |
| ? (NDS32_ISR_INTERRUPT) |
| : (NDS32_ISR_EXCEPTION); |
| strcpy (nds32_isr_vectors[vector_id].func_name, func_name); |
| |
| /* Set register saving scheme. */ |
| if (save_all) |
| nds32_isr_vectors[vector_id].save_reg = NDS32_SAVE_ALL; |
| else if (partial_save) |
| nds32_isr_vectors[vector_id].save_reg = NDS32_PARTIAL_SAVE; |
| |
| /* Set nested type. */ |
| if (nested) |
| nds32_isr_vectors[vector_id].nested_type = NDS32_NESTED; |
| else if (not_nested) |
| nds32_isr_vectors[vector_id].nested_type = NDS32_NOT_NESTED; |
| else if (nested_ready) |
| nds32_isr_vectors[vector_id].nested_type = NDS32_NESTED_READY; |
| |
| /* Advance to next id. */ |
| id_list = TREE_CHAIN (id_list); |
| } |
| } |
| else |
| { |
| tree id_list; |
| tree id; |
| tree nmi, warm; |
| |
| /* Deal with reset attribute. Its vector number is always 0. */ |
| nds32_isr_vectors[0].category = NDS32_ISR_RESET; |
| |
| /* Prepare id_list and identify id value so that |
| we can set total number of vectors. */ |
| id_list = TREE_VALUE (reset); |
| id = TREE_VALUE (id_list); |
| |
| /* The total vectors = interrupt + exception numbers + reset. |
| There are 8 exception and 1 reset in nds32 architecture. */ |
| nds32_isr_vectors[0].total_n_vectors = TREE_INT_CST_LOW (id) + 8 + 1; |
| strcpy (nds32_isr_vectors[0].func_name, func_name); |
| |
| /* Retrieve nmi and warm function. */ |
| nmi = lookup_attribute ("nmi", func_attrs); |
| warm = lookup_attribute ("warm", func_attrs); |
| |
| if (nmi != NULL_TREE) |
| { |
| tree nmi_func_list; |
| tree nmi_func; |
| |
| nmi_func_list = TREE_VALUE (nmi); |
| nmi_func = TREE_VALUE (nmi_func_list); |
| |
| /* Record nmi function name. */ |
| strcpy (nds32_isr_vectors[0].nmi_name, |
| IDENTIFIER_POINTER (nmi_func)); |
| } |
| |
| if (warm != NULL_TREE) |
| { |
| tree warm_func_list; |
| tree warm_func; |
| |
| warm_func_list = TREE_VALUE (warm); |
| warm_func = TREE_VALUE (warm_func_list); |
| |
| /* Record warm function name. */ |
| strcpy (nds32_isr_vectors[0].warm_name, |
| IDENTIFIER_POINTER (warm_func)); |
| } |
| } |
| } |
| |
| /* 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. |
| Also, we need to return zero for sp adjustment so that |
| proglogue/epilogue knows there is no need to create 'addi' instruction. */ |
| static int |
| nds32_force_addi_stack_int (int full_value) |
| { |
| int adjust_value; |
| |
| rtx tmp_reg; |
| rtx sp_adjust_insn; |
| |
| if (!satisfies_constraint_Is15 (GEN_INT (full_value))) |
| { |
| /* 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, GEN_INT (full_value)); |
| |
| /* Create new 'add' rtx. */ |
| sp_adjust_insn = gen_addsi3 (stack_pointer_rtx, |
| stack_pointer_rtx, |
| tmp_reg); |
| /* Emit rtx into insn list and receive its transformed insn rtx. */ |
| sp_adjust_insn = emit_insn (sp_adjust_insn); |
| |
| /* At prologue, we need to tell GCC that this is frame related insn, |
| so that we can consider this instruction to output debug information. |
| If full_value is NEGATIVE, it means this function |
| is invoked by expand_prologue. */ |
| if (full_value < 0) |
| { |
| /* 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, stack_pointer_rtx, full_value); |
| set_rtx = gen_rtx_SET (VOIDmode, stack_pointer_rtx, plus_rtx); |
| add_reg_note (sp_adjust_insn, REG_FRAME_RELATED_EXPR, set_rtx); |
| |
| RTX_FRAME_RELATED_P (sp_adjust_insn) = 1; |
| } |
| |
| /* We have used alternative way to adjust stack pointer value. |
| Return zero so that prologue/epilogue |
| will not generate other instructions. */ |
| return 0; |
| } |
| else |
| { |
| /* The value is able to fit in addi instruction. |
| However, remember to make it to be positive value |
| because we want to return 'adjustment' result. */ |
| adjust_value = (full_value < 0) ? (-full_value) : (full_value); |
| |
| return adjust_value; |
| } |
| } |
| |
| /* Return true if MODE/TYPE need double word alignment. */ |
| static bool |
| nds32_needs_double_word_align (enum 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 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 (enum 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'. */ |
| if (GET_MODE_SIZE (outer_mode) == 1) |
| { |
| /* Further check if the value is legal for the 'outer_mode'. */ |
| if (!satisfies_constraint_Is15 (index)) |
| return false; |
| |
| /* Pass all test, the value is valid, return true. */ |
| return true; |
| } |
| if (GET_MODE_SIZE (outer_mode) == 2 |
| && NDS32_HALF_WORD_ALIGN_P (INTVAL (index))) |
| { |
| /* Further check if the value is legal for the 'outer_mode'. */ |
| if (!satisfies_constraint_Is16 (index)) |
| return false; |
| |
| /* Pass all test, the value is valid, return true. */ |
| return true; |
| } |
| if (GET_MODE_SIZE (outer_mode) == 4 |
| && NDS32_SINGLE_WORD_ALIGN_P (INTVAL (index))) |
| { |
| /* Further check if the value is legal for the 'outer_mode'. */ |
| if (!satisfies_constraint_Is17 (index)) |
| return false; |
| |
| /* Pass all test, the value is valid, return true. */ |
| return true; |
| } |
| if (GET_MODE_SIZE (outer_mode) == 8 |
| && NDS32_SINGLE_WORD_ALIGN_P (INTVAL (index))) |
| { |
| /* Further check if the value is legal for the 'outer_mode'. */ |
| if (!satisfies_constraint_Is17 (gen_int_mode (INTVAL (index) + 4, |
| SImode))) |
| return false; |
| |
| /* Pass all test, the value is valid, return true. */ |
| return true; |
| } |
| |
| 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) |
| or (mult reg const_int_2) or (mult reg const_int_4). */ |
| if (multiplier != 1 && multiplier != 2 && multiplier != 4) |
| 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). */ |
| if (sv != 0 && sv != 1 && sv !=2) |
| 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; |
| } |
| } |
| |
| /* Function to expand builtin function for |
| '[(unspec_volatile [(reg)])]'. */ |
| static rtx |
| nds32_expand_builtin_null_ftype_reg (enum insn_code icode, |
| tree exp, rtx target) |
| { |
| /* Mapping: |
| ops[0] <--> value0 <--> arg0 */ |
| struct expand_operand ops[1]; |
| tree arg0; |
| rtx value0; |
| |
| /* Grab the incoming arguments and extract its rtx. */ |
| arg0 = CALL_EXPR_ARG (exp, 0); |
| value0 = expand_normal (arg0); |
| |
| /* Create operands. */ |
| create_input_operand (&ops[0], value0, TYPE_MODE (TREE_TYPE (arg0))); |
| |
| /* Emit new instruction. */ |
| if (!maybe_expand_insn (icode, 1, ops)) |
| error ("invalid argument to built-in function"); |
| |
| return target; |
| } |
| |
| /* Function to expand builtin function for |
| '[(set (reg) (unspec_volatile [(imm)]))]'. */ |
| static rtx |
| nds32_expand_builtin_reg_ftype_imm (enum insn_code icode, |
| tree exp, rtx target) |
| { |
| /* Mapping: |
| ops[0] <--> target <--> exp |
| ops[1] <--> value0 <--> arg0 */ |
| struct expand_operand ops[2]; |
| tree arg0; |
| rtx value0; |
| |
| /* Grab the incoming arguments and extract its rtx. */ |
| arg0 = CALL_EXPR_ARG (exp, 0); |
| value0 = expand_normal (arg0); |
| |
| /* Create operands. */ |
| create_output_operand (&ops[0], target, TYPE_MODE (TREE_TYPE (exp))); |
| create_input_operand (&ops[1], value0, TYPE_MODE (TREE_TYPE (arg0))); |
| |
| /* Emit new instruction. */ |
| if (!maybe_expand_insn (icode, 2, ops)) |
| error ("invalid argument to built-in function"); |
| |
| return target; |
| } |
| |
| /* Function to expand builtin function for |
| '[(unspec_volatile [(reg) (imm)])]' pattern. */ |
| static rtx |
| nds32_expand_builtin_null_ftype_reg_imm (enum insn_code icode, |
| tree exp, rtx target) |
| { |
| /* Mapping: |
| ops[0] <--> value0 <--> arg0 |
| ops[1] <--> value1 <--> arg1 */ |
| struct expand_operand ops[2]; |
| tree arg0, arg1; |
| rtx value0, value1; |
| |
| /* Grab the incoming arguments and extract its rtx. */ |
| arg0 = CALL_EXPR_ARG (exp, 0); |
| arg1 = CALL_EXPR_ARG (exp, 1); |
| value0 = expand_normal (arg0); |
| value1 = expand_normal (arg1); |
| |
| /* Create operands. */ |
| create_input_operand (&ops[0], value0, TYPE_MODE (TREE_TYPE (arg0))); |
| create_input_operand (&ops[1], value1, TYPE_MODE (TREE_TYPE (arg1))); |
| |
| /* Emit new instruction. */ |
| if (!maybe_expand_insn (icode, 2, ops)) |
| error ("invalid argument to built-in function"); |
| |
| return target; |
| } |
| |
| /* A helper function to return character based on byte size. */ |
| static char |
| nds32_byte_to_size (int byte) |
| { |
| switch (byte) |
| { |
| case 4: |
| return 'w'; |
| case 2: |
| return 'h'; |
| case 1: |
| return 'b'; |
| default: |
| /* Normally it should not be here. */ |
| gcc_unreachable (); |
| } |
| } |
| |
| /* A helper function to check if this function should contain prologue. */ |
| static int |
| nds32_have_prologue_p (void) |
| { |
| int i; |
| |
| for (i = 0; i < 28; i++) |
| if (NDS32_REQUIRED_CALLEE_SAVED_P (i)) |
| return 1; |
| |
| return (flag_pic |
| || NDS32_REQUIRED_CALLEE_SAVED_P (FP_REGNUM) |
| || NDS32_REQUIRED_CALLEE_SAVED_P (LP_REGNUM)); |
| } |
| |
| /* ------------------------------------------------------------------------ */ |
| |
| /* PART 3: Implement target hook stuff definitions. */ |
| |
| /* Register Classes. */ |
| |
| static unsigned char |
| nds32_class_max_nregs (reg_class_t rclass ATTRIBUTE_UNUSED, |
| enum 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 && hard_regno < 8) |
| return 4; |
| return 3; |
| } |
| |
| |
| /* 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, enum machine_mode mode, |
| const_tree type, bool named) |
| { |
| 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, they are passed on the stack. */ |
| if (!named) |
| return NULL_RTX; |
| |
| /* If there are still registers available, return it. */ |
| if (NDS32_ARG_PASS_IN_REG_P (cum->reg_offset, mode, type)) |
| { |
| /* Pick up the next available register number. */ |
| unsigned int regno; |
| |
| regno = NDS32_AVAILABLE_REGNUM_FOR_ARG (cum->reg_offset, mode, type); |
| return gen_rtx_REG (mode, regno); |
| } |
| else |
| { |
| /* No register available, return NULL_RTX. |
| The compiler will use stack to pass argument instead. */ |
| return NULL_RTX; |
| } |
| } |
| |
| static void |
| nds32_function_arg_advance (cumulative_args_t ca, enum machine_mode mode, |
| const_tree type, bool named) |
| { |
| CUMULATIVE_ARGS *cum = get_cumulative_args (ca); |
| |
| /* Advance next register for use. |
| Only named argument could be advanced. */ |
| if (named) |
| { |
| cum->reg_offset |
| = NDS32_AVAILABLE_REGNUM_FOR_ARG (cum->reg_offset, mode, type) |
| - NDS32_GPR_ARG_FIRST_REGNUM |
| + NDS32_NEED_N_REGS_FOR_ARG (mode, type); |
| } |
| } |
| |
| static unsigned int |
| nds32_function_arg_boundary (enum 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) |
| { |
| enum machine_mode mode; |
| int unsignedp; |
| |
| mode = TYPE_MODE (ret_type); |
| unsignedp = TYPE_UNSIGNED (ret_type); |
| |
| mode = promote_mode (ret_type, mode, &unsignedp); |
| |
| return gen_rtx_REG (mode, NDS32_GPR_RET_FIRST_REGNUM); |
| } |
| |
| static rtx |
| nds32_libcall_value (enum machine_mode mode, |
| const_rtx fun ATTRIBUTE_UNUSED) |
| { |
| return gen_rtx_REG (mode, NDS32_GPR_RET_FIRST_REGNUM); |
| } |
| |
| static bool |
| nds32_function_value_regno_p (const unsigned int regno) |
| { |
| return (regno == NDS32_GPR_RET_FIRST_REGNUM); |
| } |
| |
| /* -- Function Entry and Exit. */ |
| |
| /* The content produced from this function |
| will be placed before prologue body. */ |
| static void |
| nds32_asm_function_prologue (FILE *file, |
| HOST_WIDE_INT size ATTRIBUTE_UNUSED) |
| { |
| 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 < 32; 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, |
| HOST_WIDE_INT size ATTRIBUTE_UNUSED) |
| { |
| 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, %ld\n", |
| this_regno, this_regno, delta); |
| } |
| else if (satisfies_constraint_Is20 (GEN_INT (delta))) |
| { |
| fprintf (file, "\tmovi\t$ta, %ld\n", delta); |
| fprintf (file, "\tadd\t$r%d, $r%d, $ta\n", this_regno, this_regno); |
| } |
| else |
| { |
| fprintf (file, "\tsethi\t$ta, hi20(%ld)\n", delta); |
| fprintf (file, "\tori\t$ta, $ta, lo12(%ld)\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. */ |
| |
| /* 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 bool |
| nds32_strict_argument_naming (cumulative_args_t ca ATTRIBUTE_UNUSED) |
| { |
| /* Return true so that all the named arguments for FUNCTION_ARG have named=1. |
| If return false, for the variadic function, all named arguments EXCEPT |
| the last are treated as named. */ |
| 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 (enum machine_mode mode, rtx x, bool strict) |
| { |
| /* 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; |
| } |
| |
| /* 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: |
| |
| if (!TARGET_GP_DIRECT |
| && (reload_completed |
| || reload_in_progress |
| || lra_in_progress)) |
| return false; |
| |
| /* (mem (symbol_ref A)) => [symbol_ref] */ |
| return !currently_expanding_to_rtl; |
| |
| case CONST: |
| |
| if (!TARGET_GP_DIRECT |
| && (reload_completed |
| || reload_in_progress |
| || lra_in_progress)) |
| return false; |
| |
| /* (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)) |
| return true; |
| else |
| return false; |
| } |
| |
| 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: |
| if (!TARGET_GP_DIRECT) |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| |
| /* Describing Relative Costs of Operations. */ |
| |
| static int nds32_register_move_cost (enum machine_mode mode ATTRIBUTE_UNUSED, |
| reg_class_t from, |
| reg_class_t to) |
| { |
| if (from == HIGH_REGS || to == HIGH_REGS) |
| return 6; |
| |
| return 2; |
| } |
| |
| static int nds32_memory_move_cost (enum 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, |
| int code, |
| int outer_code, |
| int opno ATTRIBUTE_UNUSED, |
| int *total, |
| bool speed) |
| { |
| /* According to 'speed', goto suitable cost model section. */ |
| if (speed) |
| goto performance_cost; |
| else |
| goto size_cost; |
| |
| |
| performance_cost: |
| /* This is section for performance cost model. */ |
| |
| /* In gcc/rtl.h, the default value of COSTS_N_INSNS(N) is N*4. |
| We treat it as 4-cycle cost for each instruction |
| under performance consideration. */ |
| switch (code) |
| { |
| case SET: |
| /* For 'SET' rtx, we need to return false |
| so that it can recursively calculate costs. */ |
| return false; |
| |
| case USE: |
| /* Used in combine.c as a marker. */ |
| *total = 0; |
| break; |
| |
| case MULT: |
| *total = COSTS_N_INSNS (1); |
| break; |
| |
| case DIV: |
| case UDIV: |
| case MOD: |
| case UMOD: |
| *total = COSTS_N_INSNS (7); |
| break; |
| |
| default: |
| *total = COSTS_N_INSNS (1); |
| break; |
| } |
| |
| return true; |
| |
| |
| size_cost: |
| /* This is section for size cost model. */ |
| |
| /* In gcc/rtl.h, the default value of COSTS_N_INSNS(N) is N*4. |
| We treat it as 4-byte cost for each instruction |
| under code size consideration. */ |
| switch (code) |
| { |
| case SET: |
| /* For 'SET' rtx, we need to return false |
| so that it can recursively calculate costs. */ |
| return false; |
| |
| case USE: |
| /* Used in combine.c as a marker. */ |
| *total = 0; |
| break; |
| |
| case CONST_INT: |
| /* All instructions involving constant operation |
| need to be considered for cost evaluation. */ |
| if (outer_code == SET) |
| { |
| /* (set X imm5s), use movi55, 2-byte cost. |
| (set X imm20s), use movi, 4-byte cost. |
| (set X BIG_INT), use sethi/ori, 8-byte cost. */ |
| if (satisfies_constraint_Is05 (x)) |
| *total = COSTS_N_INSNS (1) - 2; |
| else if (satisfies_constraint_Is20 (x)) |
| *total = COSTS_N_INSNS (1); |
| else |
| *total = COSTS_N_INSNS (2); |
| } |
| else if (outer_code == PLUS || outer_code == MINUS) |
| { |
| /* Possible addi333/subi333 or subi45/addi45, 2-byte cost. |
| General case, cost 1 instruction with 4-byte. */ |
| if (satisfies_constraint_Iu05 (x)) |
| *total = COSTS_N_INSNS (1) - 2; |
| else |
| *total = COSTS_N_INSNS (1); |
| } |
| else if (outer_code == ASHIFT) |
| { |
| /* Possible slli333, 2-byte cost. |
| General case, cost 1 instruction with 4-byte. */ |
| if (satisfies_constraint_Iu03 (x)) |
| *total = COSTS_N_INSNS (1) - 2; |
| else |
| *total = COSTS_N_INSNS (1); |
| } |
| else if (outer_code == ASHIFTRT || outer_code == LSHIFTRT) |
| { |
| /* Possible srai45 or srli45, 2-byte cost. |
| General case, cost 1 instruction with 4-byte. */ |
| if (satisfies_constraint_Iu05 (x)) |
| *total = COSTS_N_INSNS (1) - 2; |
| else |
| *total = COSTS_N_INSNS (1); |
| } |
| else |
| { |
| /* For other cases, simply set it 4-byte cost. */ |
| *total = COSTS_N_INSNS (1); |
| } |
| break; |
| |
| case CONST_DOUBLE: |
| /* It requires high part and low part processing, set it 8-byte cost. */ |
| *total = COSTS_N_INSNS (2); |
| break; |
| |
| default: |
| /* For other cases, generally we set it 4-byte cost |
| and stop resurively traversing. */ |
| *total = COSTS_N_INSNS (1); |
| break; |
| } |
| |
| return true; |
| } |
| |
| static int nds32_address_cost (rtx address, |
| enum machine_mode mode ATTRIBUTE_UNUSED, |
| addr_space_t as ATTRIBUTE_UNUSED, |
| bool speed) |
| { |
| rtx plus0, plus1; |
| enum rtx_code code; |
| |
| code = GET_CODE (address); |
| |
| /* According to 'speed', goto suitable cost model section. */ |
| if (speed) |
| goto performance_cost; |
| else |
| goto size_cost; |
| |
| performance_cost: |
| /* This is section for performance cost model. */ |
| |
| /* FALLTHRU, currently we use same cost model as size_cost. */ |
| |
| size_cost: |
| /* This is section for size cost model. */ |
| |
| switch (code) |
| { |
| case POST_MODIFY: |
| case POST_INC: |
| case POST_DEC: |
| /* We encourage that rtx contains |
| POST_MODIFY/POST_INC/POST_DEC behavior. */ |
| return 0; |
| |
| case SYMBOL_REF: |
| /* We can have gp-relative load/store for symbol_ref. |
| Have it 4-byte cost. */ |
| return COSTS_N_INSNS (1); |
| |
| case CONST: |
| /* It is supposed to be the pattern (const (plus symbol_ref const_int)). |
| Have it 4-byte cost. */ |
| return COSTS_N_INSNS (1); |
| |
| case REG: |
| /* Simply return 4-byte costs. */ |
| return COSTS_N_INSNS (1); |
| |
| case PLUS: |
| /* We do not need to check if the address is a legitimate address, |
| because this hook is never called with an invalid address. |
| But we better check the range of |
| const_int value for cost, if it exists. */ |
| plus0 = XEXP (address, 0); |
| plus1 = XEXP (address, 1); |
| |
| if (REG_P (plus0) && CONST_INT_P (plus1)) |
| { |
| /* If it is possible to be lwi333/swi333 form, |
| make it 2-byte cost. */ |
| if (satisfies_constraint_Iu05 (plus1)) |
| return (COSTS_N_INSNS (1) - 2); |
| else |
| return COSTS_N_INSNS (1); |
| } |
| |
| /* For other 'plus' situation, make it cost 4-byte. */ |
| return COSTS_N_INSNS (1); |
| |
| default: |
| break; |
| } |
| |
| return COSTS_N_INSNS (4); |
| } |
| |
| |
| /* Defining the Output Assembler Language. */ |
| |
| /* -- The Overall Framework of an Assembler File. */ |
| |
| static void |
| nds32_asm_file_start (void) |
| { |
| int i; |
| |
| default_file_start (); |
| |
| /* Tell assembler which ABI we are using. */ |
| fprintf (asm_out_file, "\t! ABI version\n"); |
| 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); |
| |
| /* If user enables '-mforce-fp-as-gp' or compiles programs with -Os, |
| the compiler may produce 'la $fp,_FP_BASE_' instruction |
| at prologue for fp-as-gp optimization. |
| We should emit weak reference of _FP_BASE_ to avoid undefined reference |
| in case user does not pass '--relax' option to linker. */ |
| if (TARGET_FORCE_FP_AS_GP || optimize_size) |
| { |
| fprintf (asm_out_file, "\t! This weak reference is required to do " |
| "fp-as-gp link time optimization\n"); |
| fprintf (asm_out_file, "\t.weak\t_FP_BASE_\n"); |
| } |
| /* If user enables '-mex9', we should emit relaxation directive |
| to tell linker that this file is allowed to do ex9 optimization. */ |
| if (TARGET_EX9) |
| { |
| fprintf (asm_out_file, "\t! This relaxation directive is required " |
| "to do ex9 link time optimization\n"); |
| fprintf (asm_out_file, "\t.relax\tex9\n"); |
| } |
| |
| 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"); |
| |
| fprintf (asm_out_file, "\t! Endian setting\t: %s\n", |
| ((TARGET_BIG_ENDIAN) ? "big-endian" |
| : "little-endian")); |
| |
| 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_PERF_EXT) ? "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! GP base access\t: %s\n", |
| ((TARGET_GP_DIRECT) ? "Yes" |
| : "No")); |
| fprintf (asm_out_file, "\t! Reduced registers set\t: %s\n", |
| ((TARGET_REDUCED_REGS) ? "Yes" |
| : "No")); |
| |
| fprintf (asm_out_file, "\t! ------------------------------------\n"); |
| |
| if (optimize_size) |
| fprintf (asm_out_file, "\t! Optimization level\t: -Os\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"); |
| |
| /* Initialize isr vector information array before compiling functions. */ |
| for (i = 0; i < NDS32_N_ISR_VECTORS; i++) |
| { |
| nds32_isr_vectors[i].category = NDS32_ISR_NONE; |
| strcpy (nds32_isr_vectors[i].func_name, ""); |
| nds32_isr_vectors[i].save_reg = NDS32_PARTIAL_SAVE; |
| nds32_isr_vectors[i].nested_type = NDS32_NOT_NESTED; |
| nds32_isr_vectors[i].total_n_vectors = 0; |
| strcpy (nds32_isr_vectors[i].nmi_name, ""); |
| strcpy (nds32_isr_vectors[i].warm_name, ""); |
| } |
| } |
| |
| static void |
| nds32_asm_file_end (void) |
| { |
| int i; |
| |
| /* If all the vectors are NDS32_ISR_NONE, we can return immediately. */ |
| for (i = 0; i < NDS32_N_ISR_VECTORS; i++) |
| if (nds32_isr_vectors[i].category != NDS32_ISR_NONE) |
| break; |
| |
| if (i == NDS32_N_ISR_VECTORS) |
| return; |
| |
| /* At least one vector is NOT NDS32_ISR_NONE, |
| we should output isr vector information. */ |
| fprintf (asm_out_file, "\t! ------------------------------------\n"); |
| fprintf (asm_out_file, "\t! The isr vector information:\n"); |
| fprintf (asm_out_file, "\t! ------------------------------------\n"); |
| |
| /* Check reset handler first. Its vector number is always 0. */ |
| if (nds32_isr_vectors[0].category == NDS32_ISR_RESET) |
| { |
| nds32_emit_isr_reset_content (); |
| fprintf (asm_out_file, "\t! ------------------------------------\n"); |
| } |
| |
| /* Check other vectors, starting from vector number 1. */ |
| for (i = 1; i < NDS32_N_ISR_VECTORS; i++) |
| { |
| if (nds32_isr_vectors[i].category == NDS32_ISR_INTERRUPT |
| || nds32_isr_vectors[i].category == NDS32_ISR_EXCEPTION) |
| { |
| /* Found one vector which is interupt or exception. |
| Output its jmptbl and vector section content. */ |
| fprintf (asm_out_file, "\t! interrupt/exception vector %02d\n", i); |
| fprintf (asm_out_file, "\t! ------------------------------------\n"); |
| nds32_emit_isr_jmptbl_section (i); |
| fprintf (asm_out_file, "\t! ....................................\n"); |
| nds32_emit_isr_vector_section (i); |
| fprintf (asm_out_file, "\t! ------------------------------------\n"); |
| } |
| } |
| |
| 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) |
| { |
| int op_value; |
| |
| switch (code) |
| { |
| case 0 : |
| /* Do nothing special. */ |
| break; |
| |
| case 'V': |
| /* 'x' is supposed to be CONST_INT, get the value. */ |
| gcc_assert (CONST_INT_P (x)); |
| op_value = INTVAL (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, "%d", 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; |
| |
| 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: |
| /* 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 (XEXP (x, 0)); |
| 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, 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 + (%d)]", |
| reg_names[REGNO (op0)], (int)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 |
| 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], %d", |
| reg_names[REGNO (op0)], (int)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"); |
| |
| if (REG_P (op0)) |
| { |
| /* "[Ra], 1/2/4" or "[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. */ |
| fprintf (stream, "[%s]", reg_names[REGNO (op0)]); |
| } |
| else |
| { |
| /* The control flow is not supposed to be here. */ |
| debug_rtx (x); |
| gcc_unreachable (); |
| } |
| |
| 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; |
| } |
| } |
| |
| |
| /* Defining target-specific uses of __attribute__. */ |
| |
| /* Add some checking after merging attributes. */ |
| static tree |
| nds32_merge_decl_attributes (tree olddecl, tree newdecl) |
| { |
| tree combined_attrs; |
| |
| /* Create combined attributes. */ |
| combined_attrs = merge_attributes (DECL_ATTRIBUTES (olddecl), |
| DECL_ATTRIBUTES (newdecl)); |
| |
| /* Since newdecl is acutally a duplicate of olddecl, |
| we can take olddecl for some operations. */ |
| if (TREE_CODE (olddecl) == FUNCTION_DECL) |
| { |
| /* Check isr-specific attributes conflict. */ |
| nds32_check_isr_attrs_conflict (olddecl, combined_attrs); |
| } |
| |
| return combined_attrs; |
| } |
| |
| /* Add some checking when inserting attributes. */ |
| static void |
| nds32_insert_attributes (tree decl, tree *attributes) |
| { |
| /* For function declaration, we need to check isr-specific attributes: |
| 1. Call nds32_check_isr_attrs_conflict() to check any conflict. |
| 2. Check valid integer value for interrupt/exception. |
| 3. Check valid integer value for reset. |
| 4. Check valid function for nmi/warm. */ |
| if (TREE_CODE (decl) == FUNCTION_DECL) |
| { |
| tree func_attrs; |
| tree intr, excp, reset; |
| |
| /* Pick up function attributes. */ |
| func_attrs = *attributes; |
| |
| /* 1. Call nds32_check_isr_attrs_conflict() to check any conflict. */ |
| nds32_check_isr_attrs_conflict (decl, func_attrs); |
| |
| /* Now we are starting to check valid id value |
| for interrupt/exception/reset. |
| Note that we ONLY check its validity here. |
| To construct isr vector information, it is still performed |
| by nds32_construct_isr_vectors_information(). */ |
| intr = lookup_attribute ("interrupt", func_attrs); |
| excp = lookup_attribute ("exception", func_attrs); |
| reset = lookup_attribute ("reset", func_attrs); |
| |
| if (intr || excp) |
| { |
| /* Deal with interrupt/exception. */ |
| tree id_list; |
| unsigned int lower_bound, upper_bound; |
| |
| /* The way to handle interrupt or exception is the same, |
| we just need to take care of actual vector number. |
| For interrupt(0..63), the actual vector number is (9..72). |
| For exception(1..8), the actual vector number is (1..8). */ |
| lower_bound = (intr) ? (0) : (1); |
| upper_bound = (intr) ? (63) : (8); |
| |
| /* Prepare id list so that we can traverse id value. */ |
| id_list = (intr) ? (TREE_VALUE (intr)) : (TREE_VALUE (excp)); |
| |
| /* 2. Check valid integer value for interrupt/exception. */ |
| while (id_list) |
| { |
| tree id; |
| |
| /* Pick up each vector id value. */ |
| id = TREE_VALUE (id_list); |
| /* Issue error if it is not a valid integer value. */ |
| if (TREE_CODE (id) != INTEGER_CST |
| || TREE_INT_CST_LOW (id) < lower_bound |
| || TREE_INT_CST_LOW (id) > upper_bound) |
| error ("invalid id value for interrupt/exception attribute"); |
| |
| /* Advance to next id. */ |
| id_list = TREE_CHAIN (id_list); |
| } |
| } |
| else if (reset) |
| { |
| /* Deal with reset. */ |
| tree id_list; |
| tree id; |
| tree nmi, warm; |
| unsigned int lower_bound; |
| unsigned int upper_bound; |
| |
| /* Prepare id_list and identify id value so that |
| we can check if total number of vectors is valid. */ |
| id_list = TREE_VALUE (reset); |
| id = TREE_VALUE (id_list); |
| |
| /* The maximum numbers for user's interrupt is 64. */ |
| lower_bound = 0; |
| upper_bound = 64; |
| |
| /* 3. Check valid integer value for reset. */ |
| if (TREE_CODE (id) != INTEGER_CST |
| || TREE_INT_CST_LOW (id) < lower_bound |
| || TREE_INT_CST_LOW (id) > upper_bound) |
| error ("invalid id value for reset attribute"); |
| |
| /* 4. Check valid function for nmi/warm. */ |
| nmi = lookup_attribute ("nmi", func_attrs); |
| warm = lookup_attribute ("warm", func_attrs); |
| |
| if (nmi != NULL_TREE) |
| { |
| tree nmi_func_list; |
| tree nmi_func; |
| |
| nmi_func_list = TREE_VALUE (nmi); |
| nmi_func = TREE_VALUE (nmi_func_list); |
| |
| /* Issue error if it is not a valid nmi function. */ |
| if (TREE_CODE (nmi_func) != IDENTIFIER_NODE) |
| error ("invalid nmi function for reset attribute"); |
| } |
| |
| if (warm != NULL_TREE) |
| { |
| tree warm_func_list; |
| tree warm_func; |
| |
| warm_func_list = TREE_VALUE (warm); |
| warm_func = TREE_VALUE (warm_func_list); |
| |
| /* Issue error if it is not a valid warm function. */ |
| if (TREE_CODE (warm_func) != IDENTIFIER_NODE) |
| error ("invalid warm function for reset attribute"); |
| } |
| } |
| else |
| { |
| /* No interrupt, exception, or reset attribute is set. */ |
| return; |
| } |
| } |
| } |
| |
| static bool |
| nds32_option_pragma_parse (tree args ATTRIBUTE_UNUSED, |
| tree pop_target ATTRIBUTE_UNUSED) |
| { |
| /* Currently, we do not parse any pragma target by ourself, |
| so just simply return false. */ |
| return false; |
| } |
| |
| static void |
| nds32_option_override (void) |
| { |
| /* After all the command options have been parsed, |
| we shall deal with some flags for changing compiler settings. */ |
| |
| /* At first, we check if we have to strictly |
| set some flags based on ISA family. */ |
| if (TARGET_ISA_V2) |
| { |
| /* Under V2 ISA, we need to strictly disable TARGET_V3PUSH. */ |
| target_flags &= ~MASK_V3PUSH; |
| } |
| if (TARGET_ISA_V3) |
| { |
| /* Under V3 ISA, currently nothing should be strictly set. */ |
| } |
| if (TARGET_ISA_V3M) |
| { |
| /* Under V3M ISA, we need to strictly enable TARGET_REDUCED_REGS. */ |
| target_flags |= MASK_REDUCED_REGS; |
| /* Under V3M ISA, we need to strictly disable TARGET_PERF_EXT. */ |
| target_flags &= ~MASK_PERF_EXT; |
| } |
| |
| /* See if we are using reduced-set registers: |
| $r0~$r5, $r6~$r10, $r15, $r28, $r29, $r30, $r31 |
| If so, we must forbid using $r11~$r14, $r16~$r27. */ |
| if (TARGET_REDUCED_REGS) |
| { |
| int r; |
| |
| /* Prevent register allocator from |
| choosing it as doing register allocation. */ |
| for (r = 11; r <= 14; r++) |
| fixed_regs[r] = call_used_regs[r] = 1; |
| for (r = 16; r <= 27; r++) |
| fixed_regs[r] = call_used_regs[r] = 1; |
| } |
| |
| /* See if user explicitly would like to use fp-as-gp optimization. |
| If so, we must prevent $fp from being allocated |
| during register allocation. */ |
| if (TARGET_FORCE_FP_AS_GP) |
| fixed_regs[FP_REGNUM] = call_used_regs[FP_REGNUM] = 1; |
| |
| if (!TARGET_16_BIT) |
| { |
| /* Under no 16 bit ISA, we need to strictly disable TARGET_V3PUSH. */ |
| target_flags &= ~MASK_V3PUSH; |
| } |
| |
| /* Currently, we don't support PIC code generation yet. */ |
| if (flag_pic) |
| sorry ("not support -fpic"); |
| } |
| |
| |
| /* Miscellaneous Parameters. */ |
| |
| static void |
| nds32_init_builtins (void) |
| { |
| tree pointer_type_node = build_pointer_type (integer_type_node); |
| |
| tree void_ftype_void = build_function_type (void_type_node, |
| void_list_node); |
| |
| tree void_ftype_pint = build_function_type_list (void_type_node, |
| pointer_type_node, |
| NULL_TREE); |
| |
| tree int_ftype_int = build_function_type_list (integer_type_node, |
| integer_type_node, |
| NULL_TREE); |
| |
| tree void_ftype_int_int = build_function_type_list (void_type_node, |
| integer_type_node, |
| integer_type_node, |
| NULL_TREE); |
| |
| /* Cache. */ |
| add_builtin_function ("__builtin_nds32_isync", void_ftype_pint, |
| NDS32_BUILTIN_ISYNC, |
| BUILT_IN_MD, NULL, NULL_TREE); |
| add_builtin_function ("__builtin_nds32_isb", void_ftype_void, |
| NDS32_BUILTIN_ISB, |
| BUILT_IN_MD, NULL, NULL_TREE); |
| |
| /* Register Transfer. */ |
| add_builtin_function ("__builtin_nds32_mfsr", int_ftype_int, |
| NDS32_BUILTIN_MFSR, |
| BUILT_IN_MD, NULL, NULL_TREE); |
| add_builtin_function ("__builtin_nds32_mfusr", int_ftype_int, |
| NDS32_BUILTIN_MFUSR, |
| BUILT_IN_MD, NULL, NULL_TREE); |
| add_builtin_function ("__builtin_nds32_mtsr", void_ftype_int_int, |
| NDS32_BUILTIN_MTSR, |
| BUILT_IN_MD, NULL, NULL_TREE); |
| add_builtin_function ("__builtin_nds32_mtusr", void_ftype_int_int, |
| NDS32_BUILTIN_MTUSR, |
| BUILT_IN_MD, NULL, NULL_TREE); |
| |
| /* Interrupt. */ |
| add_builtin_function ("__builtin_nds32_setgie_en", void_ftype_void, |
| NDS32_BUILTIN_SETGIE_EN, |
| BUILT_IN_MD, NULL, NULL_TREE); |
| add_builtin_function ("__builtin_nds32_setgie_dis", void_ftype_void, |
| NDS32_BUILTIN_SETGIE_DIS, |
| BUILT_IN_MD, NULL, NULL_TREE); |
| } |
| |
| static rtx |
| nds32_expand_builtin (tree exp, |
| rtx target, |
| rtx subtarget ATTRIBUTE_UNUSED, |
| enum machine_mode mode ATTRIBUTE_UNUSED, |
| int ignore ATTRIBUTE_UNUSED) |
| { |
| tree fndecl = TREE_OPERAND (CALL_EXPR_FN (exp), 0); |
| |
| int fcode = DECL_FUNCTION_CODE (fndecl); |
| |
| switch (fcode) |
| { |
| /* Cache. */ |
| case NDS32_BUILTIN_ISYNC: |
| return nds32_expand_builtin_null_ftype_reg |
| (CODE_FOR_unspec_volatile_isync, exp, target); |
| case NDS32_BUILTIN_ISB: |
| /* Since there are no result and operands for isb instruciton, |
| we can simply emit this rtx. */ |
| emit_insn (gen_unspec_volatile_isb ()); |
| return target; |
| |
| /* Register Transfer. */ |
| case NDS32_BUILTIN_MFSR: |
| return nds32_expand_builtin_reg_ftype_imm |
| (CODE_FOR_unspec_volatile_mfsr, exp, target); |
| case NDS32_BUILTIN_MFUSR: |
| return nds32_expand_builtin_reg_ftype_imm |
| (CODE_FOR_unspec_volatile_mfusr, exp, target); |
| case NDS32_BUILTIN_MTSR: |
| return nds32_expand_builtin_null_ftype_reg_imm |
| (CODE_FOR_unspec_volatile_mtsr, exp, target); |
| case NDS32_BUILTIN_MTUSR: |
| return nds32_expand_builtin_null_ftype_reg_imm |
| (CODE_FOR_unspec_volatile_mtusr, exp, target); |
| |
| /* Interrupt. */ |
| case NDS32_BUILTIN_SETGIE_EN: |
| /* Since there are no result and operands for setgie.e instruciton, |
| we can simply emit this rtx. */ |
| emit_insn (gen_unspec_volatile_setgie_en ()); |
| return target; |
| case NDS32_BUILTIN_SETGIE_DIS: |
| /* Since there are no result and operands for setgie.d instruciton, |
| we can simply emit this rtx. */ |
| emit_insn (gen_unspec_volatile_setgie_dis ()); |
| return target; |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| return NULL_RTX; |
| } |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| |
| /* PART 4: Implemet extern function definitions, |
| the prototype is in nds32-protos.h. */ |
| |
| /* Defining Data Structures for Per-function Information. */ |
| |
| void |
| nds32_init_expanders (void) |
| { |
| /* Arrange to initialize and mark the machine per-function status. */ |
| init_machine_status = nds32_init_machine_status; |
| } |
| |
| |
| /* Register Usage. */ |
| |
| /* -- How Values Fit in Registers. */ |
| |
| int |
| nds32_hard_regno_nregs (int regno ATTRIBUTE_UNUSED, |
| enum machine_mode mode) |
| { |
| return ((GET_MODE_SIZE (mode) + UNITS_PER_WORD - 1) / UNITS_PER_WORD); |
| } |
| |
| int |
| nds32_hard_regno_mode_ok (int regno, enum machine_mode mode) |
| { |
| /* Restrict double-word quantities to even register pairs. */ |
| if (HARD_REGNO_NREGS (regno, mode) == 1 |
| || !((regno) & 1)) |
| return 1; |
| |
| return 0; |
| } |
| |
| |
| /* Register Classes. */ |
| |
| enum reg_class |
| nds32_regno_reg_class (int regno) |
| { |
| /* Refer to nds32.h for more register class details. */ |
| |
| if (regno >= 0 && regno <= 7) |
| return LOW_REGS; |
| else if (regno >= 8 && regno <= 11) |
| return MIDDLE_REGS; |
| else if (regno >= 12 && regno <= 14) |
| return HIGH_REGS; |
| else if (regno == 15) |
| return R15_TA_REG; |
| else if (regno >= 16 && regno <= 19) |
| return MIDDLE_REGS; |
| else if (regno >= 20 && regno <= 31) |
| return HIGH_REGS; |
| else if (regno == 32 || regno == 33) |
| return FRAME_REGS; |
| else |
| return NO_REGS; |
| } |
| |
| |
| /* Stack Layout and Calling Conventions. */ |
| |
| /* -- Basic Stack Layout. */ |
| |
| rtx |
| nds32_return_addr_rtx (int count, |
| rtx frameaddr ATTRIBUTE_UNUSED) |
| { |
| /* There is no way to determine the return address |
| if frameaddr is the frame that has 'count' steps |
| up from current frame. */ |
| if (count != 0) |
| return NULL_RTX; |
| |
| /* If count == 0, it means we are at current frame, |
| the return address is $r30 ($lp). */ |
| return get_hard_reg_initial_val (Pmode, LP_REGNUM); |
| } |
| |
| /* -- Eliminating Frame Pointer and Arg Pointer. */ |
| |
| HOST_WIDE_INT |
| nds32_initial_elimination_offset (unsigned int from_reg, unsigned int to_reg) |
| { |
| HOST_WIDE_INT offset; |
| |
| /* Compute and setup stack frame size. |
| The result will be in cfun->machine. */ |
| nds32_compute_stack_frame (); |
| |
| /* Remember to consider |
| cfun->machine->callee_saved_area_padding_bytes |
| when calculating offset. */ |
| if (from_reg == ARG_POINTER_REGNUM && to_reg == STACK_POINTER_REGNUM) |
| { |
| offset = (cfun->machine->fp_size |
| + cfun->machine->gp_size |
| + cfun->machine->lp_size |
| + cfun->machine->callee_saved_regs_size |
| + cfun->machine->callee_saved_area_padding_bytes |
| + cfun->machine->local_size |
| + cfun->machine->out_args_size); |
| } |
| else if (from_reg == ARG_POINTER_REGNUM |
| && to_reg == HARD_FRAME_POINTER_REGNUM) |
| { |
| offset = 0; |
| } |
| else if (from_reg == FRAME_POINTER_REGNUM |
| && to_reg == STACK_POINTER_REGNUM) |
| { |
| offset = (cfun->machine->local_size + cfun->machine->out_args_size); |
| } |
| else if (from_reg == FRAME_POINTER_REGNUM |
| && to_reg == HARD_FRAME_POINTER_REGNUM) |
| { |
| offset = (-1) * (cfun->machine->fp_size |
| + cfun->machine->gp_size |
| + cfun->machine->lp_size |
| + cfun->machine->callee_saved_regs_size |
| + cfun->machine->callee_saved_area_padding_bytes); |
| } |
| else |
| { |
| gcc_unreachable (); |
| } |
| |
| return offset; |
| } |
| |
| /* -- Passing Arguments in Registers. */ |
| |
| void |
| nds32_init_cumulative_args (CUMULATIVE_ARGS *cum, |
| tree fntype ATTRIBUTE_UNUSED, |
| rtx libname ATTRIBUTE_UNUSED, |
| tree fndecl ATTRIBUTE_UNUSED, |
| int n_named_args ATTRIBUTE_UNUSED) |
| { |
| /* Initial available registers |
| (in offset, corresponding to NDS32_GPR_ARG_FIRST_REGNUM) |
| for passing arguments. */ |
| cum->reg_offset = 0; |
| } |
| |
| /* -- Function Entry and Exit. */ |
| |
| /* Function for normal multiple push prologue. */ |
| void |
| nds32_expand_prologue (void) |
| { |
| int fp_adjust; |
| int sp_adjust; |
| int en4_const; |
| |
| rtx Rb, Re; |
| rtx push_insn; |
| rtx fp_adjust_insn, sp_adjust_insn; |
| |
| /* Before computing everything for stack frame size, |
| we check if it is still worth to use fp_as_gp optimization. |
| If it is, the 'df_regs_ever_live_p (FP_REGNUM)' will be set |
| so that $fp will be saved on stack. */ |
| cfun->machine->fp_as_gp_p = nds32_fp_as_gp_check_available (); |
| |
| /* Compute and setup stack frame size. |
| The result will be in cfun->machine. */ |
| nds32_compute_stack_frame (); |
| |
| /* If the function is 'naked', |
| we do not have to generate prologue code fragment. */ |
| if (cfun->machine->naked_p) |
| return; |
| |
| /* Get callee_first_regno and callee_last_regno. */ |
| Rb = gen_rtx_REG (SImode, cfun->machine->callee_saved_regs_first_regno); |
| Re = gen_rtx_REG (SImode, cfun->machine->callee_saved_regs_last_regno); |
| |
| /* push_insn = gen_stack_push_multiple(first_regno, last_regno), |
| the pattern 'stack_push_multiple' is implemented in nds32.md. |
| For En4 field, we have to calculate its constant value. |
| Refer to Andes ISA for more information. */ |
| en4_const = 0; |
| if (cfun->machine->fp_size) |
| en4_const += 8; |
| if (cfun->machine->gp_size) |
| en4_const += 4; |
| if (cfun->machine->lp_size) |
| en4_const += 2; |
| |
| /* If $fp, $gp, $lp, and all callee-save registers are NOT required |
| to be saved, we don't have to create multiple push instruction. |
| Otherwise, a multiple push instruction is needed. */ |
| if (!(REGNO (Rb) == SP_REGNUM && REGNO (Re) == SP_REGNUM && en4_const == 0)) |
| { |
| /* Create multiple push instruction rtx. */ |
| push_insn = nds32_gen_stack_push_multiple (Rb, Re, GEN_INT (en4_const)); |
| /* Emit rtx into instructions list and receive INSN rtx form. */ |
| push_insn = emit_insn (push_insn); |
| |
| /* The insn rtx 'push_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 (push_insn) = 1; |
| } |
| |
| /* Check frame_pointer_needed to see |
| if we shall emit fp adjustment instruction. */ |
| if (frame_pointer_needed) |
| { |
| /* adjust $fp = $sp + ($fp size) + ($gp size) + ($lp size) |
| + (4 * callee-saved-registers) |
| Note: No need to adjust |
| cfun->machine->callee_saved_area_padding_bytes, |
| because, at this point, stack pointer is just |
| at the position after push instruction. */ |
| fp_adjust = cfun->machine->fp_size |
| + cfun->machine->gp_size |
| + cfun->machine->lp_size |
| + cfun->machine->callee_saved_regs_size; |
| fp_adjust_insn = gen_addsi3 (hard_frame_pointer_rtx, |
| stack_pointer_rtx, |
| GEN_INT (fp_adjust)); |
| /* Emit rtx into instructions list and receive INSN rtx form. */ |
| fp_adjust_insn = emit_insn (fp_adjust_insn); |
| } |
| |
| /* Adjust $sp = $sp - local_size - out_args_size |
| - callee_saved_area_padding_bytes. */ |
| sp_adjust = cfun->machine->local_size |
| + cfun->machine->out_args_size |
| + cfun->machine->callee_saved_area_padding_bytes; |
| /* sp_adjust value may be out of range of the addi instruction, |
| create alternative add behavior with TA_REGNUM if necessary, |
| using NEGATIVE value to tell that we are decreasing address. */ |
| sp_adjust = nds32_force_addi_stack_int ( (-1) * sp_adjust); |
| if (sp_adjust) |
| { |
| /* Generate sp adjustment instruction if and only if sp_adjust != 0. */ |
| sp_adjust_insn = gen_addsi3 (stack_pointer_rtx, |
| stack_pointer_rtx, |
| GEN_INT (-1 * sp_adjust)); |
| /* Emit rtx into instructions list and receive INSN rtx form. */ |
| sp_adjust_insn = emit_insn (sp_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 (sp_adjust_insn) = 1; |
| } |
| |
| /* Prevent the instruction scheduler from |
| moving instructions across the boundary. */ |
| emit_insn (gen_blockage ()); |
| } |
| |
| /* Function for normal multiple pop epilogue. */ |
| void |
| nds32_expand_epilogue (void) |
| { |
| int sp_adjust; |
| int en4_const; |
| |
| rtx Rb, Re; |
| rtx pop_insn; |
| rtx sp_adjust_insn; |
| |
| /* Compute and setup stack frame size. |
| The result will be in cfun->machine. */ |
| nds32_compute_stack_frame (); |
| |
| /* Prevent the instruction scheduler from |
| moving instructions across the boundary. */ |
| emit_insn (gen_blockage ()); |
| |
| /* If the function is 'naked', we do not have to generate |
| epilogue code fragment BUT 'ret' instruction. */ |
| if (cfun->machine->naked_p) |
| { |
| /* Generate return instruction by using |
| unspec_volatile_func_return pattern. |
| Make sure this instruction is after gen_blockage(). |
| NOTE that $lp will become 'live' |
| after this instruction has been emitted. */ |
| emit_insn (gen_unspec_volatile_func_return ()); |
| return; |
| } |
| |
| if (frame_pointer_needed) |
| { |
| /* adjust $sp = $fp - ($fp size) - ($gp size) - ($lp size) |
| - (4 * callee-saved-registers) |
| Note: No need to adjust |
| cfun->machine->callee_saved_area_padding_bytes, |
| because we want to adjust stack pointer |
| to the position for pop instruction. */ |
| sp_adjust = cfun->machine->fp_size |
| + cfun->machine->gp_size |
| + cfun->machine->lp_size |
| + cfun->machine->callee_saved_regs_size; |
| sp_adjust_insn = gen_addsi3 (stack_pointer_rtx, |
| hard_frame_pointer_rtx, |
| GEN_INT (-1 * sp_adjust)); |
| /* Emit rtx into instructions list and receive INSN rtx form. */ |
| sp_adjust_insn = emit_insn (sp_adjust_insn); |
| } |
| else |
| { |
| /* If frame pointer is NOT needed, |
| we cannot calculate the sp adjustment from frame pointer. |
| Instead, we calculate the adjustment by local_size, |
| out_args_size, and callee_saved_area_padding_bytes. |
| Notice that such sp adjustment value may be out of range, |
| so we have to deal with it as well. */ |
| |
| /* Adjust $sp = $sp + local_size + out_args_size |
| + callee_saved_area_padding_bytes. */ |
| sp_adjust = cfun->machine->local_size |
| + cfun->machine->out_args_size |
| + cfun->machine->callee_saved_area_padding_bytes; |
| /* sp_adjust value may be out of range of the addi instruction, |
| create alternative add behavior with TA_REGNUM if necessary, |
| using POSITIVE value to tell that we are increasing address. */ |
| sp_adjust = nds32_force_addi_stack_int (sp_adjust); |
| if (sp_adjust) |
| { |
| /* Generate sp adjustment instruction |
| if and only if sp_adjust != 0. */ |
| sp_adjust_insn = gen_addsi3 (stack_pointer_rtx, |
| stack_pointer_rtx, |
| GEN_INT (sp_adjust)); |
| /* Emit rtx into instructions list and receive INSN rtx form. */ |
| sp_adjust_insn = emit_insn (sp_adjust_insn); |
| } |
| } |
| |
| /* Get callee_first_regno and callee_last_regno. */ |
| Rb = gen_rtx_REG (SImode, cfun->machine->callee_saved_regs_first_regno); |
| Re = gen_rtx_REG (SImode, cfun->machine->callee_saved_regs_last_regno); |
| |
| /* pop_insn = gen_stack_pop_multiple(first_regno, last_regno), |
| the pattern 'stack_pop_multiple' is implementad in nds32.md. |
| For En4 field, we have to calculate its constant value. |
| Refer to Andes ISA for more information. */ |
| en4_const = 0; |
| if (cfun->machine->fp_size) |
| en4_const += 8; |
| if (cfun->machine->gp_size) |
| en4_const += 4; |
| if (cfun->machine->lp_size) |
| en4_const += 2; |
| |
| /* If $fp, $gp, $lp, and all callee-save registers are NOT required |
| to be saved, we don't have to create multiple pop instruction. |
| Otherwise, a multiple pop instruction is needed. */ |
| if (!(REGNO (Rb) == SP_REGNUM && REGNO (Re) == SP_REGNUM && en4_const == 0)) |
| { |
| /* Create multiple pop instruction rtx. */ |
| pop_insn = nds32_gen_stack_pop_multiple (Rb, Re, GEN_INT (en4_const)); |
| /* Emit pop instruction. */ |
| emit_insn (pop_insn); |
| } |
| |
| /* Generate return instruction by using |
| unspec_volatile_func_return pattern. */ |
| emit_insn (gen_unspec_volatile_func_return ()); |
| } |
| |
| /* Function for v3push prologue. */ |
| void |
| nds32_expand_prologue_v3push (void) |
| { |
| int fp_adjust; |
| int sp_adjust; |
| |
| rtx Rb, Re; |
| rtx push_insn; |
| rtx fp_adjust_insn, sp_adjust_insn; |
| |
| /* Before computing everything for stack frame size, |
| we check if it is still worth to use fp_as_gp optimization. |
| If it is, the 'df_regs_ever_live_p (FP_REGNUM)' will be set |
| so that $fp will be saved on stack. */ |
| cfun->machine->fp_as_gp_p = nds32_fp_as_gp_check_available (); |
| |
| /* Compute and setup stack frame size. |
| The result will be in cfun->machine. */ |
| nds32_compute_stack_frame (); |
| |
| /* If the function is 'naked', |
| we do not have to generate prologue code fragment. */ |
| if (cfun->machine->naked_p) |
| return; |
| |
| /* Get callee_first_regno and callee_last_regno. */ |
| Rb = gen_rtx_REG (SImode, cfun->machine->callee_saved_regs_first_regno); |
| Re = gen_rtx_REG (SImode, cfun->machine->callee_saved_regs_last_regno); |
| |
| /* Calculate sp_adjust first to test if 'push25 Re,imm8u' is available, |
| where imm8u has to be 8-byte alignment. */ |
| sp_adjust = cfun->machine->local_size |
| + cfun->machine->out_args_size |
| + cfun->machine->callee_saved_area_padding_bytes; |
| |
| if (satisfies_constraint_Iu08 (GEN_INT (sp_adjust)) |
| && NDS32_DOUBLE_WORD_ALIGN_P (sp_adjust)) |
| { |
| /* We can use 'push25 Re,imm8u'. */ |
| |
| /* push_insn = gen_stack_v3push(last_regno, sp_adjust), |
| the pattern 'stack_v3push' is implemented in nds32.md. |
| The (const_int 14) means v3push always push { $fp $gp $lp }. */ |
| push_insn = nds32_gen_stack_v3push (Rb, Re, |
| GEN_INT (14), GEN_INT (sp_adjust)); |
| /* emit rtx into instructions list and receive INSN rtx form */ |
| push_insn = emit_insn (push_insn); |
| |
| /* The insn rtx 'push_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 (push_insn) = 1; |
| |
| /* Check frame_pointer_needed to see |
| if we shall emit fp adjustment instruction. */ |
| if (frame_pointer_needed) |
| { |
| /* adjust $fp = $sp + 4 ($fp size) |
| + 4 ($gp size) |
| + 4 ($lp size) |
| + (4 * n) (callee-saved registers) |
| + sp_adjust ('push25 Re,imm8u') |
| Note: Since we use 'push25 Re,imm8u', |
| the position of stack pointer is further |
| changed after push instruction. |
| Hence, we need to take sp_adjust value |
| into consideration. */ |
| fp_adjust = cfun->machine->fp_size |
| + cfun->machine->gp_size |
| + cfun->machine->lp_size |
| + cfun->machine->callee_saved_regs_size |
| + sp_adjust; |
| fp_adjust_insn = gen_addsi3 (hard_frame_pointer_rtx, |
| stack_pointer_rtx, |
| GEN_INT (fp_adjust)); |
| /* Emit rtx into instructions list and receive INSN rtx form. */ |
| fp_adjust_insn = emit_insn (fp_adjust_insn); |
| } |
| } |
| else |
| { |
| /* We have to use 'push25 Re,0' and |
| expand one more instruction to adjust $sp later. */ |
| |
| /* push_insn = gen_stack_v3push(last_regno, sp_adjust), |
| the pattern 'stack_v3push' is implemented in nds32.md. |
| The (const_int 14) means v3push always push { $fp $gp $lp }. */ |
| push_insn = nds32_gen_stack_v3push (Rb, Re, |
| GEN_INT (14), GEN_INT (0)); |
| /* Emit rtx into instructions list and receive INSN rtx form. */ |
| push_insn = emit_insn (push_insn); |
| |
| /* The insn rtx 'push_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 (push_insn) = 1; |
| |
| /* Check frame_pointer_needed to see |
| if we shall emit fp adjustment instruction. */ |
| if (frame_pointer_needed) |
| { |
| /* adjust $fp = $sp + 4 ($fp size) |
| + 4 ($gp size) |
| + 4 ($lp size) |
| + (4 * n) (callee-saved registers) |
| Note: Since we use 'push25 Re,0', |
| the stack pointer is just at the position |
| after push instruction. |
| No need to take sp_adjust into consideration. */ |
| fp_adjust = cfun->machine->fp_size |
| + cfun->machine->gp_size |
| + cfun->machine->lp_size |
| + cfun->machine->callee_saved_regs_size; |
| fp_adjust_insn = gen_addsi3 (hard_frame_pointer_rtx, |
| stack_pointer_rtx, |
| GEN_INT (fp_adjust)); |
| /* Emit rtx into instructions list and receive INSN rtx form. */ |
| fp_adjust_insn = emit_insn (fp_adjust_insn); |
| } |
| |
| /* Because we use 'push25 Re,0', |
| we need to expand one more instruction to adjust $sp. |
| However, sp_adjust value may be out of range of the addi instruction, |
| create alternative add behavior with TA_REGNUM if necessary, |
| using NEGATIVE value to tell that we are decreasing address. */ |
| sp_adjust = nds32_force_addi_stack_int ( (-1) * sp_adjust); |
| if (sp_adjust) |
| { |
| /* Generate sp adjustment instruction |
| if and only if sp_adjust != 0. */ |
| sp_adjust_insn = gen_addsi3 (stack_pointer_rtx, |
| stack_pointer_rtx, |
| GEN_INT (-1 * sp_adjust)); |
| /* Emit rtx into instructions list and receive INSN rtx form. */ |
| sp_adjust_insn = emit_insn (sp_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 (sp_adjust_insn) = 1; |
| } |
| } |
| |
| /* Prevent the instruction scheduler from |
| moving instructions across the boundary. */ |
| emit_insn (gen_blockage ()); |
| } |
| |
| /* Function for v3pop epilogue. */ |
| void |
| nds32_expand_epilogue_v3pop (void) |
| { |
| int sp_adjust; |
| |
| rtx Rb, Re; |
| rtx pop_insn; |
| rtx sp_adjust_insn; |
| |
| /* Compute and setup stack frame size. |
| The result will be in cfun->machine. */ |
| nds32_compute_stack_frame (); |
| |
| /* Prevent the instruction scheduler from |
| moving instructions across the boundary. */ |
| emit_insn (gen_blockage ()); |
| |
| /* If the function is 'naked', we do not have to generate |
| epilogue code fragment BUT 'ret' instruction. */ |
| if (cfun->machine->naked_p) |
| { |
| /* Generate return instruction by using |
| unspec_volatile_func_return pattern. |
| Make sure this instruction is after gen_blockage(). |
| NOTE that $lp will become 'live' |
| after this instruction has been emitted. */ |
| emit_insn (gen_unspec_volatile_func_return ()); |
| return; |
| } |
| |
| /* Get callee_first_regno and callee_last_regno. */ |
| Rb = gen_rtx_REG (SImode, cfun->machine->callee_saved_regs_first_regno); |
| Re = gen_rtx_REG (SImode, cfun->machine->callee_saved_regs_last_regno); |
| |
| /* Calculate sp_adjust first to test if 'pop25 Re,imm8u' is available, |
| where imm8u has to be 8-byte alignment. */ |
| sp_adjust = cfun->machine->local_size |
| + cfun->machine->out_args_size |
| + cfun->machine->callee_saved_area_padding_bytes; |
| |
| /* We have to consider alloca issue as well. |
| If the function does call alloca(), the stack pointer is not fixed. |
| In that case, we cannot use 'pop25 Re,imm8u' directly. |
| We have to caculate stack pointer from frame pointer |
| and then use 'pop25 Re,0'. |
| Of course, the frame_pointer_needed should be nonzero |
| if the function calls alloca(). */ |
| if (satisfies_constraint_Iu08 (GEN_INT (sp_adjust)) |
| && NDS32_DOUBLE_WORD_ALIGN_P (sp_adjust) |
| && !cfun->calls_alloca) |
| { |
| /* We can use 'pop25 Re,imm8u'. */ |
| |
| /* pop_insn = gen_stack_v3pop(last_regno, sp_adjust), |
| the pattern 'stack_v3pop' is implementad in nds32.md. |
| The (const_int 14) means v3pop always pop { $fp $gp $lp }. */ |
| pop_insn = nds32_gen_stack_v3pop (Rb, Re, |
| GEN_INT (14), GEN_INT (sp_adjust)); |
| |
| /* Emit pop instruction. */ |
| emit_insn (pop_insn); |
| } |
| else |
| { |
| /* We have to use 'pop25 Re,0', and prior to it, |
| we must expand one more instruction to adjust $sp. */ |
| |
| if (frame_pointer_needed) |
| { |
| /* adjust $sp = $fp - 4 ($fp size) |
| - 4 ($gp size) |
| - 4 ($lp size) |
| - (4 * n) (callee-saved registers) |
| Note: No need to adjust |
| cfun->machine->callee_saved_area_padding_bytes, |
| because we want to adjust stack pointer |
| to the position for pop instruction. */ |
| sp_adjust = cfun->machine->fp_size |
| + cfun->machine->gp_size |
| + cfun->machine->lp_size |
| + cfun->machine->callee_saved_regs_size; |
| sp_adjust_insn = gen_addsi3 (stack_pointer_rtx, |
| hard_frame_pointer_rtx, |
| GEN_INT (-1 * sp_adjust)); |
| /* Emit rtx into instructions list and receive INSN rtx form. */ |
| sp_adjust_insn = emit_insn (sp_adjust_insn); |
| } |
| else |
| { |
| /* If frame pointer is NOT needed, |
| we cannot calculate the sp adjustment from frame pointer. |
| Instead, we calculate the adjustment by local_size, |
| out_args_size, and callee_saved_area_padding_bytes. |
| Notice that such sp adjustment value may be out of range, |
| so we have to deal with it as well. */ |
| |
| /* Adjust $sp = $sp + local_size + out_args_size |
| + callee_saved_area_padding_bytes. */ |
| sp_adjust = cfun->machine->local_size |
| + cfun->machine->out_args_size |
| + cfun->machine->callee_saved_area_padding_bytes; |
| /* sp_adjust value may be out of range of the addi instruction, |
| create alternative add behavior with TA_REGNUM if necessary, |
| using POSITIVE value to tell that we are increasing address. */ |
| sp_adjust = nds32_force_addi_stack_int (sp_adjust); |
| if (sp_adjust) |
| { |
| /* Generate sp adjustment instruction |
| if and only if sp_adjust != 0. */ |
| sp_adjust_insn = gen_addsi3 (stack_pointer_rtx, |
| stack_pointer_rtx, |
| GEN_INT (sp_adjust)); |
| /* Emit rtx into instructions list and receive INSN rtx form. */ |
| sp_adjust_insn = emit_insn (sp_adjust_insn); |
| } |
| } |
| |
| /* pop_insn = gen_stack_v3pop(last_regno, sp_adjust), |
| the pattern 'stack_v3pop' is implementad in nds32.md. */ |
| /* The (const_int 14) means v3pop always pop { $fp $gp $lp }. */ |
| pop_insn = nds32_gen_stack_v3pop (Rb, Re, |
| GEN_INT (14), GEN_INT (0)); |
| |
| /* Emit pop instruction. */ |
| emit_insn (pop_insn); |
| } |
| } |
| |
| /* ------------------------------------------------------------------------ */ |
| |
| /* Function to test 333-form for load/store instructions. |
| This is auxiliary extern function for auxiliary macro in nds32.h. |
| Because it is a little complicated, we use function instead of macro. */ |
| bool |
| nds32_ls_333_p (rtx rt, rtx ra, rtx imm, enum machine_mode mode) |
| { |
| if (REGNO_REG_CLASS (REGNO (rt)) == LOW_REGS |
| && REGNO_REG_CLASS (REGNO (ra)) == LOW_REGS) |
| { |
| if (GET_MODE_SIZE (mode) == 4) |
| return satisfies_constraint_Iu05 (imm); |
| |
| if (GET_MODE_SIZE (mode) == 2) |
| return satisfies_constraint_Iu04 (imm); |
| |
| if (GET_MODE_SIZE (mode) == 1) |
| return satisfies_constraint_Iu03 (imm); |
| } |
| |
| return false; |
| } |
| |
| |
| /* Functions to expand load_multiple and store_multiple. |
| They are auxiliary extern functions to help create rtx template. |
| Check nds32-multiple.md file for the patterns. */ |
| rtx |
| nds32_expand_load_multiple (int base_regno, int count, |
| rtx base_addr, rtx basemem) |
| { |
| int par_index; |
| int offset; |
| rtx result; |
| rtx new_addr, mem, reg; |
| |
| /* Create the pattern that is presented in nds32-multiple.md. */ |
| |
| result = gen_rtx_PARALLEL (VOIDmode, rtvec_alloc (count)); |
| |
| for (par_index = 0; par_index < count; par_index++) |
| { |
| offset = par_index * 4; |
| /* 4-byte for loading data to each register. */ |
| new_addr = plus_constant (Pmode, base_addr, offset); |
| mem = adjust_automodify_address_nv (basemem, SImode, |
| new_addr, offset); |
| reg = gen_rtx_REG (SImode, base_regno + par_index); |
| |
| XVECEXP (result, 0, par_index) = gen_rtx_SET (VOIDmode, reg, mem); |
| } |
| |
| return result; |
| } |
| |
| rtx |
| nds32_expand_store_multiple (int base_regno, int count, |
| rtx base_addr, rtx basemem) |
| { |
| int par_index; |
| int offset; |
| rtx result; |
| rtx new_addr, mem, reg; |
| |
| /* Create the pattern that is presented in nds32-multiple.md. */ |
| |
| result = gen_rtx_PARALLEL (VOIDmode, rtvec_alloc (count)); |
| |
| for (par_index = 0; par_index < count; par_index++) |
| { |
| offset = par_index * 4; |
| /* 4-byte for storing data to memory. */ |
| new_addr = plus_constant (Pmode, base_addr, offset); |
| mem = adjust_automodify_address_nv (basemem, SImode, |
| new_addr, offset); |
| reg = gen_rtx_REG (SImode, base_regno + par_index); |
| |
| XVECEXP (result, 0, par_index) = gen_rtx_SET (VOIDmode, mem, reg); |
| } |
| |
| return result; |
| } |
| |
| /* Function to move block memory content by |
| using load_multiple and store_multiple. |
| This is auxiliary extern function to help create rtx template. |
| Check nds32-multiple.md file for the patterns. */ |
| int |
| nds32_expand_movmemqi (rtx dstmem, rtx srcmem, rtx total_bytes, rtx alignment) |
| { |
| HOST_WIDE_INT in_words, out_words; |
| rtx dst_base_reg, src_base_reg; |
| int maximum_bytes; |
| |
| /* Because reduced-set regsiters has few registers |
| (r0~r5, r6~10, r15, r28~r31, where 'r15' and 'r28~r31' |
| cannot be used for register allocation), |
| using 8 registers (32 bytes) for moving memory block |
| may easily consume all of them. |
| It makes register allocation/spilling hard to work. |
| So we only allow maximum=4 registers (16 bytes) for |
| moving memory block under reduced-set registers. */ |
| if (TARGET_REDUCED_REGS) |
| maximum_bytes = 16; |
| else |
| maximum_bytes = 32; |
| |
| /* 1. Total_bytes is integer for sure. |
| 2. Alignment is integer for sure. |
| 3. Maximum 4 or 8 registers, 4 * 4 = 16 bytes, 8 * 4 = 32 bytes. |
| 4. Requires (n * 4) block size. |
| 5. Requires 4-byte alignment. */ |
| if (GET_CODE (total_bytes) != CONST_INT |
| || GET_CODE (alignment) != CONST_INT |
| || INTVAL (total_bytes) > maximum_bytes |
| || INTVAL (total_bytes) & 3 |
| || INTVAL (alignment) & 3) |
| return 0; |
| |
| dst_base_reg = copy_to_mode_reg (SImode, XEXP (dstmem, 0)); |
| src_base_reg = copy_to_mode_reg (SImode, XEXP (srcmem, 0)); |
| |
| out_words = in_words = INTVAL (total_bytes) / UNITS_PER_WORD; |
| |
| emit_insn (nds32_expand_load_multiple (0, in_words, src_base_reg, srcmem)); |
| emit_insn (nds32_expand_store_multiple (0, out_words, dst_base_reg, dstmem)); |
| |
| /* Successfully create patterns, return 1. */ |
| return 1; |
| } |
| |
| /* Function to check whether the OP is a valid load/store operation. |
| This is a helper function for the predicates: |
| 'nds32_load_multiple_operation' and 'nds32_store_multiple_operation' |
| in predicates.md file. |
| |
| The OP is supposed to be a parallel rtx. |
| For each element within this parallel rtx: |
| (set (reg) (mem addr)) is the form for load operation. |
| (set (mem addr) (reg)) is the form for store operation. |
| We have to extract reg and mem of every element and |
| check if the information is valid for multiple load/store operation. */ |
| bool |
| nds32_valid_multiple_load_store (rtx op, bool load_p) |
| { |
| int count; |
| int first_elt_regno; |
| rtx elt; |
| |
| /* Get the counts of elements in the parallel rtx. */ |
| count = XVECLEN (op, 0); |
| /* Pick up the first element. */ |
| elt = XVECEXP (op, 0, 0); |
| |
| /* Perform some quick check for the first element in the parallel rtx. */ |
| if (GET_CODE (elt) != SET |
| || count <= 1 |
| || count > 8) |
| return false; |
| |
| /* Pick up regno of first element for further detail checking. |
| Note that the form is different between load and store operation. */ |
| if (load_p) |
| { |
| if (GET_CODE (SET_DEST (elt)) != REG |
| || GET_CODE (SET_SRC (elt)) != MEM) |
| return false; |
| |
| first_elt_regno = REGNO (SET_DEST (elt)); |
| } |
| else |
| { |
| if (GET_CODE (SET_SRC (elt)) != REG |
| || GET_CODE (SET_DEST (elt)) != MEM) |
| return false; |
| |
| first_elt_regno = REGNO (SET_SRC (elt)); |
| } |
| |
| /* Perform detail check for each element. |
| Refer to nds32-multiple.md for more information |
| about following checking. |
| The starting element of parallel rtx is index 0. */ |
| if (!nds32_consecutive_registers_load_store_p (op, load_p, 0, |
| first_elt_regno, |
| count)) |
| return false; |
| |
| /* Pass all test, this is a valid rtx. */ |
| return true; |
| } |
| |
| /* Function to check whether the OP is a valid stack push/pop operation. |
| For a valid stack operation, it must satisfy following conditions: |
| 1. Consecutive registers push/pop operations. |
| 2. Valid $fp/$gp/$lp push/pop operations. |
| 3. The last element must be stack adjustment rtx. |
| See the prologue/epilogue implementation for details. */ |
| bool |
| nds32_valid_stack_push_pop (rtx op, bool push_p) |
| { |
| int index; |
| int total_count; |
| int rest_count; |
| int first_regno; |
| rtx elt; |
| rtx elt_reg; |
| rtx elt_mem; |
| rtx elt_plus; |
| |
| /* Get the counts of elements in the parallel rtx. */ |
| total_count = XVECLEN (op, 0); |
| |
| /* Perform some quick check for that every element should be 'set'. */ |
| for (index = 0; index < total_count; index++) |
| { |
| elt = XVECEXP (op, 0, index); |
| if (GET_CODE (elt) != SET) |
| return false; |
| } |
| |
| /* For push operation, the parallel rtx looks like: |
| (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)))]) |
| |
| For pop operation, the parallel rtx looks like: |
| (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)))]) */ |
| |
| /* 1. Consecutive registers push/pop operations. |
| We need to calculate how many registers should be consecutive. |
| The $sp adjustment rtx, $fp push rtx, $gp push rtx, |
| and $lp push rtx are excluded. */ |
| |
| /* Exclude last $sp adjustment rtx. */ |
| rest_count = total_count - 1; |
| /* Exclude $fp, $gp, and $lp if they are in the parallel rtx. */ |
| if (cfun->machine->fp_size) |
| rest_count--; |
| if (cfun->machine->gp_size) |
| rest_count--; |
| if (cfun->machine->lp_size) |
| rest_count--; |
| |
| if (rest_count > 0) |
| { |
| elt = XVECEXP (op, 0, 0); |
| /* Pick up register element. */ |
| elt_reg = push_p ? SET_SRC (elt) : SET_DEST (elt); |
| first_regno = REGNO (elt_reg); |
| |
| /* The 'push' operation is a kind of store operation. |
| The 'pop' operation is a kind of load operation. |
| Pass corresponding false/true as second argument (bool load_p). |
| The par_index is supposed to start with index 0. */ |
| if (!nds32_consecutive_registers_load_store_p (op, |
| !push_p ? true : false, |
| 0, |
| first_regno, |
| rest_count)) |
| return false; |
| } |
| |
| /* 2. Valid $fp/$gp/$lp push/pop operations. |
| Remember to set start index for checking them. */ |
| |
| /* The rest_count is the start index for checking $fp/$gp/$lp. */ |
| index = rest_count; |
| /* If index < 0, this parallel rtx is definitely |
| not a valid stack push/pop operation. */ |
| if (index < 0) |
| return false; |
| |
| /* Check $fp/$gp/$lp one by one. |
| We use 'push_p' to pick up reg rtx and mem rtx. */ |
| if (cfun->machine->fp_size) |
| { |
| elt = XVECEXP (op, 0, index); |
| elt_mem = push_p ? SET_DEST (elt) : SET_SRC (elt); |
| elt_reg = push_p ? SET_SRC (elt) : SET_DEST (elt); |
| index++; |
| |
| if (GET_CODE (elt_mem) != MEM |
| || GET_CODE (elt_reg) != REG |
| || REGNO (elt_reg) != FP_REGNUM) |
| return false; |
| } |
| if (cfun->machine->gp_size) |
| { |
| elt = XVECEXP (op, 0, index); |
| elt_mem = push_p ? SET_DEST (elt) : SET_SRC (elt); |
| elt_reg = push_p ? SET_SRC (elt) : SET_DEST (elt); |
| index++; |
| |
| if (GET_CODE (elt_mem) != MEM |
| || GET_CODE (elt_reg) != REG |
| || REGNO (elt_reg) != GP_REGNUM) |
| return false; |
| } |
| if (cfun->machine->lp_size) |
| { |
| elt = XVECEXP (op, 0, index); |
| elt_mem = push_p ? SET_DEST (elt) : SET_SRC (elt); |
| elt_reg = push_p ? SET_SRC (elt) : SET_DEST (elt); |
| index++; |
| |
| if (GET_CODE (elt_mem) != MEM |
| || GET_CODE (elt_reg) != REG |
| || REGNO (elt_reg) != LP_REGNUM) |
| return false; |
| } |
| |
| /* 3. The last element must be stack adjustment rtx. |
| Its form of rtx should be: |
| (set (reg:SI SP_REGNUM) |
| (plus (reg:SI SP_REGNUM) (const_int X))) |
| The X could be positive or negative value. */ |
| |
| /* Pick up the last element. */ |
| elt = XVECEXP (op, 0, total_count - 1); |
| |
| /* Extract its destination and source rtx. */ |
| elt_reg = SET_DEST (elt); |
| elt_plus = SET_SRC (elt); |
| |
| /* Check this is (set (stack_reg) (plus stack_reg const)) pattern. */ |
| if (GET_CODE (elt_reg) != REG |
| || GET_CODE (elt_plus) != PLUS |
| || REGNO (elt_reg) != SP_REGNUM) |
| return false; |
| |
| /* Pass all test, this is a valid rtx. */ |
| return true; |
| } |
| |
| /* 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, int length) |
| { |
| rtx src, dst; |
| |
| switch (recog_memoized (insn)) |
| { |
| case CODE_FOR_move_df: |
| case CODE_FOR_move_di: |
| /* Adjust length of movd44 to 2. */ |
| src = XEXP (PATTERN (insn), 1); |
| dst = XEXP (PATTERN (insn), 0); |
| |
| if (REG_P (src) |
| && REG_P (dst) |
| && (REGNO (src) % 2) == 0 |
| && (REGNO (dst) % 2) == 0) |
| length = 2; |
| break; |
| |
| default: |
| break; |
| } |
| |
| return length; |
| } |
| |
| |
| /* Function to check if 'bclr' instruction can be used with IVAL. */ |
| int |
| nds32_can_use_bclr_p (int ival) |
| { |
| int one_bit_count; |
| |
| /* Calculate the number of 1-bit of (~ival), if there is only one 1-bit, |
| it means the original ival has only one 0-bit, |
| So it is ok to perform 'bclr' operation. */ |
| |
| one_bit_count = popcount_hwi ((unsigned HOST_WIDE_INT) (~ival)); |
| |
| /* 'bclr' is a performance extension instruction. */ |
| return (TARGET_PERF_EXT && (one_bit_count == 1)); |
| } |
| |
| /* Function to check if 'bset' instruction can be used with IVAL. */ |
| int |
| nds32_can_use_bset_p (int ival) |
| { |
| int one_bit_count; |
| |
| /* Caculate the number of 1-bit of ival, if there is only one 1-bit, |
| it is ok to perform 'bset' operation. */ |
| |
| one_bit_count = popcount_hwi ((unsigned HOST_WIDE_INT) (ival)); |
| |
| /* 'bset' is a performance extension instruction. */ |
| return (TARGET_PERF_EXT && (one_bit_count == 1)); |
| } |
| |
| /* Function to check if 'btgl' instruction can be used with IVAL. */ |
| int |
| nds32_can_use_btgl_p (int ival) |
| { |
| int one_bit_count; |
| |
| /* Caculate the number of 1-bit of ival, if there is only one 1-bit, |
| it is ok to perform 'btgl' operation. */ |
| |
| one_bit_count = popcount_hwi ((unsigned HOST_WIDE_INT) (ival)); |
| |
| /* 'btgl' is a performance extension instruction. */ |
| return (TARGET_PERF_EXT && (one_bit_count == 1)); |
| } |
| |
| /* Function to check if 'bitci' instruction can be used with IVAL. */ |
| int |
| nds32_can_use_bitci_p (int ival) |
| { |
| /* If we are using V3 ISA, we have 'bitci' instruction. |
| Try to see if we can present 'andi' semantic with |
| such 'bit-clear-immediate' operation. |
| For example, 'andi $r0,$r0,0xfffffffc' can be |
| presented with 'bitci $r0,$r0,3'. */ |
| return (TARGET_ISA_V3 |
| && (ival < 0) |
| && satisfies_constraint_Iu15 (gen_int_mode (~ival, SImode))); |
| } |
| |
| |
| /* Return true if is load/store with SYMBOL_REF addressing mode |
| and memory mode is SImode. */ |
| bool |
| nds32_symbol_load_store_p (rtx insn) |
| { |
| rtx mem_src = NULL_RTX; |
| |
| switch (get_attr_type (insn)) |
| { |
| case TYPE_LOAD: |
| mem_src = SET_SRC (PATTERN (insn)); |
| break; |
| case TYPE_STORE: |
| mem_src = SET_DEST (PATTERN (insn)); |
| break; |
| default: |
| break; |
| } |
| |
| /* Find load/store insn with addressing mode is SYMBOL_REF. */ |
| if (mem_src != NULL_RTX) |
| { |
| if ((GET_CODE (mem_src) == ZERO_EXTEND) |
| || (GET_CODE (mem_src) == SIGN_EXTEND)) |
| mem_src = XEXP (mem_src, 0); |
| |
| if ((GET_CODE (XEXP (mem_src, 0)) == SYMBOL_REF) |
| || (GET_CODE (XEXP (mem_src, 0)) == LO_SUM)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* Function to determine whether it is worth to do fp_as_gp optimization. |
| Return 0: It is NOT worth to do fp_as_gp optimization. |
| Return 1: It is APPROXIMATELY worth to do fp_as_gp optimization. |
| Note that if it is worth to do fp_as_gp optimization, |
| we MUST set FP_REGNUM ever live in this function. */ |
| int |
| nds32_fp_as_gp_check_available (void) |
| { |
| /* If there exists ANY of following conditions, |
| we DO NOT perform fp_as_gp optimization: |
| 1. TARGET_FORBID_FP_AS_GP is set |
| regardless of the TARGET_FORCE_FP_AS_GP. |
| 2. User explicitly uses 'naked' attribute. |
| 3. Not optimize for size. |
| 4. Need frame pointer. |
| 5. If $fp is already required to be saved, |
| it means $fp is already choosen by register allocator. |
| Thus we better not to use it for fp_as_gp optimization. |
| 6. This function is a vararg function. |
| DO NOT apply fp_as_gp optimization on this function |
| because it may change and break stack frame. |
| 7. The epilogue is empty. |
| This happens when the function uses exit() |
| or its attribute is no_return. |
| In that case, compiler will not expand epilogue |
| so that we have no chance to output .omit_fp_end directive. */ |
| if (TARGET_FORBID_FP_AS_GP |
| || lookup_attribute ("naked", DECL_ATTRIBUTES (current_function_decl)) |
| || !optimize_size |
| || frame_pointer_needed |
| || NDS32_REQUIRED_CALLEE_SAVED_P (FP_REGNUM) |
| || (cfun->stdarg == 1) |
| || (find_fallthru_edge (EXIT_BLOCK_PTR_FOR_FN (cfun)->preds) == NULL)) |
| return 0; |
| |
| /* Now we can check the possibility of using fp_as_gp optimization. */ |
| if (TARGET_FORCE_FP_AS_GP) |
| { |
| /* User explicitly issues -mforce-fp-as-gp option. */ |
| df_set_regs_ever_live (FP_REGNUM, 1); |
| return 1; |
| } |
| else |
| { |
| /* In the following we are going to evaluate whether |
| it is worth to do fp_as_gp optimization. */ |
| int good_gain = 0; |
| int symbol_count = 0; |
| |
| int threshold; |
| rtx insn; |
| |
| /* We check if there already requires prologue. |
| Note that $gp will be saved in prologue for PIC code generation. |
| After that, we can set threshold by the existence of prologue. |
| Each fp-implied instruction will gain 2-byte code size |
| from gp-aware instruction, so we have following heuristics. */ |
| if (flag_pic |
| || nds32_have_prologue_p ()) |
| { |
| /* Have-prologue: |
| Compiler already intends to generate prologue content, |
| so the fp_as_gp optimization will only insert |
| 'la $fp,_FP_BASE_' instruction, which will be |
| converted into 4-byte instruction at link time. |
| The threshold is "3" symbol accesses, 2 + 2 + 2 > 4. */ |
| threshold = 3; |
| } |
| else |
| { |
| /* None-prologue: |
| Compiler originally does not generate prologue content, |
| so the fp_as_gp optimization will NOT ONLY insert |
| 'la $fp,_FP_BASE' instruction, but also causes |
| push/pop instructions. |
| If we are using v3push (push25/pop25), |
| the threshold is "5" symbol accesses, 5*2 > 4 + 2 + 2; |
| If we are using normal push (smw/lmw), |
| the threshold is "5+2" symbol accesses 7*2 > 4 + 4 + 4. */ |
| threshold = 5 + (TARGET_V3PUSH ? 0 : 2); |
| } |
| |
| /* We would like to traverse every instruction in this function. |
| So we need to have push_topmost_sequence()/pop_topmost_sequence() |
| surrounding our for-loop evaluation. */ |
| push_topmost_sequence (); |
| /* Counting the insn number which the addressing mode is symbol. */ |
| for (insn = get_insns (); insn; insn = NEXT_INSN (insn)) |
| { |
| if (single_set (insn) && nds32_symbol_load_store_p (insn)) |
| symbol_count++; |
| |
| if (symbol_count == threshold) |
| { |
| good_gain = 1; |
| break; |
| } |
| } |
| pop_topmost_sequence (); |
| |
| /* Enable fp_as_gp optimization when potential gain is good enough. */ |
| if (good_gain) |
| { |
| df_set_regs_ever_live (FP_REGNUM, 1); |
| return 1; |
| } |
| } |
| |
| /* By default we return 0. */ |
| return 0; |
| } |
| |
| |
| /* Function to generate PC relative jump table. |
| Refer to nds32.md for more details. |
| |
| The following is the sample for the case that diff value |
| can be presented in '.short' size. |
| |
| addi $r1, $r1, -(case_lower_bound) |
| slti $ta, $r1, (case_number) |
| beqz $ta, .L_skip_label |
| |
| la $ta, .L35 ! get jump table address |
| lh $r1, [$ta + $r1 << 1] ! load symbol diff from jump table entry |
| addi $ta, $r1, $ta |
| jr5 $ta |
| |
| ! jump table entry |
| L35: |
| .short .L25-.L35 |
| .short .L26-.L35 |
| .short .L27-.L35 |
| .short .L28-.L35 |
| .short .L29-.L35 |
| .short .L30-.L35 |
| .short .L31-.L35 |
| .short .L32-.L35 |
| .short .L33-.L35 |
| .short .L34-.L35 */ |
| const char * |
| nds32_output_casesi_pc_relative (rtx *operands) |
| { |
| enum machine_mode mode; |
| rtx diff_vec; |
| |
| diff_vec = PATTERN (NEXT_INSN (operands[1])); |
| |
| gcc_assert (GET_CODE (diff_vec) == ADDR_DIFF_VEC); |
| |
| /* Step C: "t <-- operands[1]". */ |
| output_asm_insn ("la\t$ta, %l1", operands); |
| |
| /* Get the mode of each element in the difference vector. */ |
| mode = GET_MODE (diff_vec); |
| |
| /* Step D: "z <-- (mem (plus (operands[0] << m) t))", |
| where m is 0, 1, or 2 to load address-diff value from table. */ |
| switch (mode) |
| { |
| case QImode: |
| output_asm_insn ("lb\t%2, [$ta + %0 << 0]", operands); |
| break; |
| case HImode: |
| output_asm_insn ("lh\t%2, [$ta + %0 << 1]", operands); |
| break; |
| case SImode: |
| output_asm_insn ("lw\t%2, [$ta + %0 << 2]", operands); |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| |
| /* Step E: "t <-- z + t". |
| Add table label_ref with address-diff value to |
| obtain target case address. */ |
| output_asm_insn ("add\t$ta, %2, $ta", operands); |
| |
| /* Step F: jump to target with register t. */ |
| if (TARGET_16_BIT) |
| return "jr5\t$ta"; |
| else |
| return "jr\t$ta"; |
| } |
| |
| /* Function to generate normal jump table. */ |
| const char * |
| nds32_output_casesi (rtx *operands) |
| { |
| /* Step C: "t <-- operands[1]". */ |
| output_asm_insn ("la\t$ta, %l1", operands); |
| |
| /* Step D: "z <-- (mem (plus (operands[0] << 2) t))". */ |
| output_asm_insn ("lw\t%2, [$ta + %0 << 2]", operands); |
| |
| /* No need to perform Step E, which is only used for |
| pc relative jump table. */ |
| |
| /* Step F: jump to target with register z. */ |
| if (TARGET_16_BIT) |
| return "jr5\t%2"; |
| else |
| return "jr\t%2"; |
| } |
| |
| |
| /* Function to return memory format. */ |
| enum nds32_16bit_address_type |
| nds32_mem_format (rtx op) |
| { |
| enum machine_mode mode_test; |
| int val; |
| int regno; |
| |
| if (!TARGET_16_BIT) |
| return ADDRESS_NOT_16BIT_FORMAT; |
| |
| mode_test = GET_MODE (op); |
| |
| op = XEXP (op, 0); |
| |
| /* 45 format. */ |
| if (GET_CODE (op) == REG && (mode_test == SImode)) |
| return ADDRESS_REG; |
| |
| /* 333 format for QI/HImode. */ |
| if (GET_CODE (op) == REG && (REGNO (op) < R8_REGNUM)) |
| return ADDRESS_LO_REG_IMM3U; |
| |
| /* post_inc 333 format. */ |
| if ((GET_CODE (op) == POST_INC) && (mode_test == SImode)) |
| { |
| regno = REGNO(XEXP (op, 0)); |
| |
| if (regno < 8) |
| return ADDRESS_POST_INC_LO_REG_IMM3U; |
| } |
| |
| /* post_inc 333 format. */ |
| if ((GET_CODE (op) == POST_MODIFY) |
| && (mode_test == SImode) |
| && (REG_P (XEXP (XEXP (op, 1), 0))) |
| && (CONST_INT_P (XEXP (XEXP (op, 1), 1)))) |
| { |
| regno = REGNO (XEXP (XEXP (op, 1), 0)); |
| val = INTVAL (XEXP (XEXP (op, 1), 1)); |
| if (regno < 8 && val < 32) |
| return ADDRESS_POST_INC_LO_REG_IMM3U; |
| } |
| |
| if ((GET_CODE (op) == PLUS) |
| && (GET_CODE (XEXP (op, 0)) == REG) |
| && (GET_CODE (XEXP (op, 1)) == CONST_INT)) |
| { |
| val = INTVAL (XEXP (op, 1)); |
| |
| regno = REGNO(XEXP (op, 0)); |
| |
| if (regno > 7 |
| && regno != SP_REGNUM |
| && regno != FP_REGNUM) |
| return ADDRESS_NOT_16BIT_FORMAT; |
| |
| switch (mode_test) |
| { |
| case QImode: |
| /* 333 format. */ |
| if (val >= 0 && val < 8 && regno < 8) |
| return ADDRESS_LO_REG_IMM3U; |
| break; |
| |
| case HImode: |
| /* 333 format. */ |
| if (val >= 0 && val < 16 && (val % 2 == 0) && regno < 8) |
| return ADDRESS_LO_REG_IMM3U; |
| break; |
| |
| case SImode: |
| case SFmode: |
| case DFmode: |
| /* fp imply 37 format. */ |
| if ((regno == FP_REGNUM) && |
| (val >= 0 && val < 512 && (val % 4 == 0))) |
| return ADDRESS_FP_IMM7U; |
| /* sp imply 37 format. */ |
| else if ((regno == SP_REGNUM) && |
| (val >= 0 && val < 512 && (val % 4 == 0))) |
| return ADDRESS_SP_IMM7U; |
| /* 333 format. */ |
| else if (val >= 0 && val < 32 && (val % 4 == 0) && regno < 8) |
| return ADDRESS_LO_REG_IMM3U; |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| return ADDRESS_NOT_16BIT_FORMAT; |
| } |
| |
| /* Output 16-bit store. */ |
| const char * |
| nds32_output_16bit_store (rtx *operands, int byte) |
| { |
| char pattern[100]; |
| char size; |
| rtx code = XEXP (operands[0], 0); |
| |
| size = nds32_byte_to_size (byte); |
| |
| switch (nds32_mem_format (operands[0])) |
| { |
| case ADDRESS_REG: |
| operands[0] = code; |
| output_asm_insn ("swi450\t%1, [%0]", operands); |
| break; |
| case ADDRESS_LO_REG_IMM3U: |
| snprintf (pattern, sizeof (pattern), "s%ci333\t%%1, %%0", size); |
| output_asm_insn (pattern, operands); |
| break; |
| case ADDRESS_POST_INC_LO_REG_IMM3U: |
| snprintf (pattern, sizeof (pattern), "s%ci333.bi\t%%1, %%0", size); |
| output_asm_insn (pattern, operands); |
| break; |
| case ADDRESS_FP_IMM7U: |
| output_asm_insn ("swi37\t%1, %0", operands); |
| break; |
| case ADDRESS_SP_IMM7U: |
| /* Get immediate value and set back to operands[1]. */ |
| operands[0] = XEXP (code, 1); |
| output_asm_insn ("swi37.sp\t%1, [ + (%0)]", operands); |
| break; |
| default: |
| break; |
| } |
| |
| return ""; |
| } |
| |
| /* Output 16-bit load. */ |
| const char * |
| nds32_output_16bit_load (rtx *operands, int byte) |
| { |
| char pattern[100]; |
| unsigned char size; |
| rtx code = XEXP (operands[1], 0); |
| |
| size = nds32_byte_to_size (byte); |
| |
| switch (nds32_mem_format (operands[1])) |
| { |
| case ADDRESS_REG: |
| operands[1] = code; |
| output_asm_insn ("lwi450\t%0, [%1]", operands); |
| break; |
| case ADDRESS_LO_REG_IMM3U: |
| snprintf (pattern, sizeof (pattern), "l%ci333\t%%0, %%1", size); |
| output_asm_insn (pattern, operands); |
| break; |
| case ADDRESS_POST_INC_LO_REG_IMM3U: |
| snprintf (pattern, sizeof (pattern), "l%ci333.bi\t%%0, %%1", size); |
| output_asm_insn (pattern, operands); |
| break; |
| case ADDRESS_FP_IMM7U: |
| output_asm_insn ("lwi37\t%0, %1", operands); |
| break; |
| case ADDRESS_SP_IMM7U: |
| /* Get immediate value and set back to operands[0]. */ |
| operands[1] = XEXP (code, 1); |
| output_asm_insn ("lwi37.sp\t%0, [ + (%1)]", operands); |
| break; |
| default: |
| break; |
| } |
| |
| return ""; |
| } |
| |
| /* Output 32-bit store. */ |
| const char * |
| nds32_output_32bit_store (rtx *operands, int byte) |
| { |
| char pattern[100]; |
| unsigned char size; |
| rtx code = XEXP (operands[0], 0); |
| |
| size = nds32_byte_to_size (byte); |
| |
| switch (GET_CODE (code)) |
| { |
| case REG: |
| /* (mem (reg X)) |
| => access location by using register, |
| use "sbi / shi / swi" */ |
| snprintf (pattern, sizeof (pattern), "s%ci\t%%1, %%0", size); |
| break; |
| |
| case SYMBOL_REF: |
| case CONST: |
| /* (mem (symbol_ref X)) |
| (mem (const (...))) |
| => access global variables, |
| use "sbi.gp / shi.gp / swi.gp" */ |
| operands[0] = XEXP (operands[0], 0); |
| snprintf (pattern, sizeof (pattern), "s%ci.gp\t%%1, [ + %%0]", size); |
| break; |
| |
| case POST_INC: |
| /* (mem (post_inc reg)) |
| => access location by using register which will be post increment, |
| use "sbi.bi / shi.bi / swi.bi" */ |
| snprintf (pattern, sizeof (pattern), |
| "s%ci.bi\t%%1, %%0, %d", size, byte); |
| break; |
| |
| case POST_DEC: |
| /* (mem (post_dec reg)) |
| => access location by using register which will be post decrement, |
| use "sbi.bi / shi.bi / swi.bi" */ |
| snprintf (pattern, sizeof (pattern), |
| "s%ci.bi\t%%1, %%0, -%d", size, byte); |
| break; |
| |
| case POST_MODIFY: |
| switch (GET_CODE (XEXP (XEXP (code, 1), 1))) |
| { |
| case REG: |
| case SUBREG: |
| /* (mem (post_modify (reg) (plus (reg) (reg)))) |
| => access location by using register which will be |
| post modified with reg, |
| use "sb.bi/ sh.bi / sw.bi" */ |
| snprintf (pattern, sizeof (pattern), "s%c.bi\t%%1, %%0", size); |
| break; |
| case CONST_INT: |
| /* (mem (post_modify (reg) (plus (reg) (const_int)))) |
| => access location by using register which will be |
| post modified with const_int, |
| use "sbi.bi/ shi.bi / swi.bi" */ |
| snprintf (pattern, sizeof (pattern), "s%ci.bi\t%%1, %%0", size); |
| break; |
| default: |
| abort (); |
| } |
| break; |
| |
| case PLUS: |
| switch (GET_CODE (XEXP (code, 1))) |
| { |
| case REG: |
| case SUBREG: |
| /* (mem (plus reg reg)) or (mem (plus (mult reg const_int) reg)) |
| => access location by adding two registers, |
| use "sb / sh / sw" */ |
| snprintf (pattern, sizeof (pattern), "s%c\t%%1, %%0", size); |
| break; |
| case CONST_INT: |
| /* (mem (plus reg const_int)) |
| => access location by adding one register with const_int, |
| use "sbi / shi / swi" */ |
| snprintf (pattern, sizeof (pattern), "s%ci\t%%1, %%0", size); |
| break; |
| default: |
| abort (); |
| } |
| break; |
| |
| case LO_SUM: |
| operands[2] = XEXP (code, 1); |
| operands[0] = XEXP (code, 0); |
| snprintf (pattern, sizeof (pattern), |
| "s%ci\t%%1, [%%0 + lo12(%%2)]", size); |
| break; |
| |
| default: |
| abort (); |
| } |
| |
| output_asm_insn (pattern, operands); |
| return ""; |
| } |
| |
| /* Output 32-bit load. */ |
| const char * |
| nds32_output_32bit_load (rtx *operands, int byte) |
| { |
| char pattern[100]; |
| unsigned char size; |
| rtx code; |
| |
| code = XEXP (operands[1], 0); |
| |
| size = nds32_byte_to_size (byte); |
| |
| switch (GET_CODE (code)) |
| { |
| case REG: |
| /* (mem (reg X)) |
| => access location by using register, |
| use "lbi / lhi / lwi" */ |
| snprintf (pattern, sizeof (pattern), "l%ci\t%%0, %%1", size); |
| break; |
| |
| case SYMBOL_REF: |
| case CONST: |
| /* (mem (symbol_ref X)) |
| (mem (const (...))) |
| => access global variables, |
| use "lbi.gp / lhi.gp / lwi.gp" */ |
| operands[1] = XEXP (operands[1], 0); |
| snprintf (pattern, sizeof (pattern), "l%ci.gp\t%%0, [ + %%1]", size); |
| break; |
| |
| case POST_INC: |
| /* (mem (post_inc reg)) |
| => access location by using register which will be post increment, |
| use "lbi.bi / lhi.bi / lwi.bi" */ |
| snprintf (pattern, sizeof (pattern), |
| "l%ci.bi\t%%0, %%1, %d", size, byte); |
| break; |
| |
| case POST_DEC: |
| /* (mem (post_dec reg)) |
| => access location by using register which will be post decrement, |
| use "lbi.bi / lhi.bi / lwi.bi" */ |
| snprintf (pattern, sizeof (pattern), |
| "l%ci.bi\t%%0, %%1, -%d", size, byte); |
| break; |
| |
| case POST_MODIFY: |
| switch (GET_CODE (XEXP (XEXP (code, 1), 1))) |
| { |
| case REG: |
| case SUBREG: |
| /* (mem (post_modify (reg) (plus (reg) (reg)))) |
| => access location by using register which will be |
| post modified with reg, |
| use "lb.bi/ lh.bi / lw.bi" */ |
| snprintf (pattern, sizeof (pattern), "l%c.bi\t%%0, %%1", size); |
| break; |
| case CONST_INT: |
| /* (mem (post_modify (reg) (plus (reg) (const_int)))) |
| => access location by using register which will be |
| post modified with const_int, |
| use "lbi.bi/ lhi.bi / lwi.bi" */ |
| snprintf (pattern, sizeof (pattern), "l%ci.bi\t%%0, %%1", size); |
| break; |
| default: |
| abort (); |
| } |
| break; |
| |
| case PLUS: |
| switch (GET_CODE (XEXP (code, 1))) |
| { |
| case REG: |
| case SUBREG: |
| /* (mem (plus reg reg)) or (mem (plus (mult reg const_int) reg)) |
| use "lb / lh / lw" */ |
| snprintf (pattern, sizeof (pattern), "l%c\t%%0, %%1", size); |
| break; |
| case CONST_INT: |
| /* (mem (plus reg const_int)) |
| => access location by adding one register with const_int, |
| use "lbi / lhi / lwi" */ |
| snprintf (pattern, sizeof (pattern), "l%ci\t%%0, %%1", size); |
| break; |
| default: |
| abort (); |
| } |
| break; |
| |
| case LO_SUM: |
| operands[2] = XEXP (code, 1); |
| operands[1] = XEXP (code, 0); |
| snprintf (pattern, sizeof (pattern), |
| "l%ci\t%%0, [%%1 + lo12(%%2)]", size); |
| break; |
| |
| default: |
| abort (); |
| } |
| |
| output_asm_insn (pattern, operands); |
| return ""; |
| } |
| |
| /* Output 32-bit load with signed extension. */ |
| const char * |
| nds32_output_32bit_load_s (rtx *operands, int byte) |
| { |
| char pattern[100]; |
| unsigned char size; |
| rtx code; |
| |
| code = XEXP (operands[1], 0); |
| |
| size = nds32_byte_to_size (byte); |
| |
| switch (GET_CODE (code)) |
| { |
| case REG: |
| /* (mem (reg X)) |
| => access location by using register, |
| use "lbsi / lhsi" */ |
| snprintf (pattern, sizeof (pattern), "l%csi\t%%0, %%1", size); |
| break; |
| |
| case SYMBOL_REF: |
| case CONST: |
| /* (mem (symbol_ref X)) |
| (mem (const (...))) |
| => access global variables, |
| use "lbsi.gp / lhsi.gp" */ |
| operands[1] = XEXP (operands[1], 0); |
| snprintf (pattern, sizeof (pattern), "l%csi.gp\t%%0, [ + %%1]", size); |
| break; |
| |
| case POST_INC: |
| /* (mem (post_inc reg)) |
| => access location by using register which will be post increment, |
| use "lbsi.bi / lhsi.bi" */ |
| snprintf (pattern, sizeof (pattern), |
| "l%csi.bi\t%%0, %%1, %d", size, byte); |
| break; |
| |
| case POST_DEC: |
| /* (mem (post_dec reg)) |
| => access location by using register which will be post decrement, |
| use "lbsi.bi / lhsi.bi" */ |
| snprintf (pattern, sizeof (pattern), |
| "l%csi.bi\t%%0, %%1, -%d", size, byte); |
| break; |
| |
| case POST_MODIFY: |
| switch (GET_CODE (XEXP (XEXP (code, 1), 1))) |
| { |
| case REG: |
| case SUBREG: |
| /* (mem (post_modify (reg) (plus (reg) (reg)))) |
| => access location by using register which will be |
| post modified with reg, |
| use "lbs.bi/ lhs.bi" */ |
| snprintf (pattern, sizeof (pattern), "l%cs.bi\t%%0, %%1", size); |
| break; |
| case CONST_INT: |
| /* (mem (post_modify (reg) (plus (reg) (const_int)))) |
| => access location by using register which will be |
| post modified with const_int, |
| use "lbsi.bi/ lhsi.bi" */ |
| snprintf (pattern, sizeof (pattern), "l%csi.bi\t%%0, %%1", size); |
| break; |
| default: |
| abort (); |
| } |
| break; |
| |
| case PLUS: |
| switch (GET_CODE (XEXP (code, 1))) |
| { |
| case REG: |
| case SUBREG: |
| /* (mem (plus reg reg)) or (mem (plus (mult reg const_int) reg)) |
| use "lbs / lhs" */ |
| snprintf (pattern, sizeof (pattern), "l%cs\t%%0, %%1", size); |
| break; |
| case CONST_INT: |
| /* (mem (plus reg const_int)) |
| => access location by adding one register with const_int, |
| use "lbsi / lhsi" */ |
| snprintf (pattern, sizeof (pattern), "l%csi\t%%0, %%1", size); |
| break; |
| default: |
| abort (); |
| } |
| break; |
| |
| case LO_SUM: |
| operands[2] = XEXP (code, 1); |
| operands[1] = XEXP (code, 0); |
| snprintf (pattern, sizeof (pattern), |
| "l%csi\t%%0, [%%1 + lo12(%%2)]", size); |
| break; |
| |
| default: |
| abort (); |
| } |
| |
| output_asm_insn (pattern, operands); |
| return ""; |
| } |
| |
| /* Function to output stack push operation. |
| We need to deal with normal stack push multiple or stack v3push. */ |
| const char * |
| nds32_output_stack_push (void) |
| { |
| /* A string pattern for output_asm_insn(). */ |
| char pattern[100]; |
| /* The operands array which will be used in output_asm_insn(). */ |
| rtx operands[3]; |
| /* Pick up callee-saved first regno and last regno for further use. */ |
| int rb_regno = cfun->machine->callee_saved_regs_first_regno; |
| int re_regno = cfun->machine->callee_saved_regs_last_regno; |
| |
| if (TARGET_V3PUSH) |
| { |
| /* For stack v3push: |
| operands[0]: Re |
| operands[1]: imm8u */ |
| |
| /* This variable is to check if 'push25 Re,imm8u' is available. */ |
| int sp_adjust; |
| |
| /* Set operands[0]. */ |
| operands[0] = gen_rtx_REG (SImode, re_regno); |
| |
| /* Check if we can generate 'push25 Re,imm8u', |
| otherwise, generate 'push25 Re,0'. */ |
| sp_adjust = cfun->machine->local_size |
| + cfun->machine->out_args_size |
| + cfun->machine->callee_saved_area_padding_bytes; |
| if (satisfies_constraint_Iu08 (GEN_INT (sp_adjust)) |
| && NDS32_DOUBLE_WORD_ALIGN_P (sp_adjust)) |
| operands[1] = GEN_INT (sp_adjust); |
| else |
| operands[1] = GEN_INT (0); |
| |
| /* Create assembly code pattern. */ |
| snprintf (pattern, sizeof (pattern), "push25\t%%0, %%1"); |
| } |
| else |
| { |
| /* For normal stack push multiple: |
| operands[0]: Rb |
| operands[1]: Re |
| operands[2]: En4 */ |
| |
| /* This variable is used to check if we only need to generate En4 field. |
| As long as Rb==Re=SP_REGNUM, we set this variable to 1. */ |
| int push_en4_only_p = 0; |
| |
| /* Set operands[0] and operands[1]. */ |
| operands[0] = gen_rtx_REG (SImode, rb_regno); |
| operands[1] = gen_rtx_REG (SImode, re_regno); |
| |
| /* 'smw.adm $sp,[$sp],$sp,0' means push nothing. */ |
| if (!cfun->machine->fp_size |
| && !cfun->machine->gp_size |
| && !cfun->machine->lp_size |
| && REGNO (operands[0]) == SP_REGNUM |
| && REGNO (operands[1]) == SP_REGNUM) |
| { |
| /* No need to generate instruction. */ |
| return ""; |
| } |
| else |
| { |
| /* If Rb==Re=SP_REGNUM, we only need to generate En4 field. */ |
| if (REGNO (operands[0]) == SP_REGNUM |
| && REGNO (operands[1]) == SP_REGNUM) |
| push_en4_only_p = 1; |
| |
| /* Create assembly code pattern. |
| We need to handle the form: "Rb, Re, { $fp $gp $lp }". */ |
| snprintf (pattern, sizeof (pattern), |
| "push.s\t%s{%s%s%s }", |
| push_en4_only_p ? "" : "%0, %1, ", |
| cfun->machine->fp_size ? " $fp" : "", |
| cfun->machine->gp_size ? " $gp" : "", |
| cfun->machine->lp_size ? " $lp" : ""); |
| } |
| } |
| |
| /* We use output_asm_insn() to output assembly code by ourself. */ |
| output_asm_insn (pattern, operands); |
| return ""; |
| } |
| |
| /* Function to output stack pop operation. |
| We need to deal with normal stack pop multiple or stack v3pop. */ |
| const char * |
| nds32_output_stack_pop (void) |
| { |
| /* A string pattern for output_asm_insn(). */ |
| char pattern[100]; |
| /* The operands array which will be used in output_asm_insn(). */ |
| rtx operands[3]; |
| /* Pick up callee-saved first regno and last regno for further use. */ |
| int rb_regno = cfun->machine->callee_saved_regs_first_regno; |
| int re_regno = cfun->machine->callee_saved_regs_last_regno; |
| |
| if (TARGET_V3PUSH) |
| { |
| /* For stack v3pop: |
| operands[0]: Re |
| operands[1]: imm8u */ |
| |
| /* This variable is to check if 'pop25 Re,imm8u' is available. */ |
| int sp_adjust; |
| |
| /* Set operands[0]. */ |
| operands[0] = gen_rtx_REG (SImode, re_regno); |
| |
| /* Check if we can generate 'pop25 Re,imm8u', |
| otherwise, generate 'pop25 Re,0'. |
| We have to consider alloca issue as well. |
| If the function does call alloca(), the stack pointer is not fixed. |
| In that case, we cannot use 'pop25 Re,imm8u' directly. |
| We have to caculate stack pointer from frame pointer |
| and then use 'pop25 Re,0'. */ |
| sp_adjust = cfun->machine->local_size |
| + cfun->machine->out_args_size |
| + cfun->machine->callee_saved_area_padding_bytes; |
| if (satisfies_constraint_Iu08 (GEN_INT (sp_adjust)) |
| && NDS32_DOUBLE_WORD_ALIGN_P (sp_adjust) |
| && !cfun->calls_alloca) |
| operands[1] = GEN_INT (sp_adjust); |
| else |
| operands[1] = GEN_INT (0); |
| |
| /* Create assembly code pattern. */ |
| snprintf (pattern, sizeof (pattern), "pop25\t%%0, %%1"); |
| } |
| else |
| { |
| /* For normal stack pop multiple: |
| operands[0]: Rb |
| operands[1]: Re |
| operands[2]: En4 */ |
| |
| /* This variable is used to check if we only need to generate En4 field. |
| As long as Rb==Re=SP_REGNUM, we set this variable to 1. */ |
| int pop_en4_only_p = 0; |
| |
| /* Set operands[0] and operands[1]. */ |
| operands[0] = gen_rtx_REG (SImode, rb_regno); |
| operands[1] = gen_rtx_REG (SImode, re_regno); |
| |
| /* 'lmw.bim $sp,[$sp],$sp,0' means pop nothing. */ |
| if (!cfun->machine->fp_size |
| && !cfun->machine->gp_size |
| && !cfun->machine->lp_size |
| && REGNO (operands[0]) == SP_REGNUM |
| && REGNO (operands[1]) == SP_REGNUM) |
| { |
| /* No need to generate instruction. */ |
| return ""; |
| } |
| else |
| { |
| /* If Rb==Re=SP_REGNUM, we only need to generate En4 field. */ |
| if (REGNO (operands[0]) == SP_REGNUM |
| && REGNO (operands[1]) == SP_REGNUM) |
| pop_en4_only_p = 1; |
| |
| /* Create assembly code pattern. |
| We need to handle the form: "Rb, Re, { $fp $gp $lp }". */ |
| snprintf (pattern, sizeof (pattern), |
| "pop.s\t%s{%s%s%s }", |
| pop_en4_only_p ? "" : "%0, %1, ", |
| cfun->machine->fp_size ? " $fp" : "", |
| cfun->machine->gp_size ? " $gp" : "", |
| cfun->machine->lp_size ? " $lp" : ""); |
| } |
| } |
| |
| /* We use output_asm_insn() to output assembly code by ourself. */ |
| output_asm_insn (pattern, operands); |
| return ""; |
| } |
| |
| /* Return align 2 (log base 2) if the next instruction of LABEL is 4 byte. */ |
| int |
| nds32_target_alignment (rtx label) |
| { |
| rtx insn; |
| |
| if (optimize_size) |
| return 0; |
| |
| insn = next_active_insn (label); |
| |
| if (insn == 0) |
| return 0; |
| else if ((get_attr_length (insn) % 4) == 0) |
| return 2; |
| else |
| return 0; |
| } |
| |
| /* ------------------------------------------------------------------------ */ |
| |
| /* PART 5: Initialize target hook structure and definitions. */ |
| |
| /* Controlling the Compilation Driver. */ |
| |
| |
| /* Run-time Target Specification. */ |
| |
| |
| /* Defining Data Structures for Per-function Information. */ |
| |
| |
| /* Storage Layout. */ |
| |
| #undef TARGET_PROMOTE_FUNCTION_MODE |
| #define TARGET_PROMOTE_FUNCTION_MODE \ |
| default_promote_function_mode_always_promote |
| |
| |
| /* Layout of Source Language Data Types. */ |
| |
| |
| /* Register Usage. */ |
| |
| /* -- Basic Characteristics of Registers. */ |
| |
| /* -- Order of Allocation of Registers. */ |
| |
| /* -- How Values Fit in Registers. */ |
| |
| /* -- Handling Leaf Functions. */ |
| |
| /* -- Registers That Form a Stack. */ |
| |
| |
| /* Register Classes. */ |
| |
| #undef TARGET_CLASS_MAX_NREGS |
| #define TARGET_CLASS_MAX_NREGS nds32_class_max_nregs |
| |
| #undef TARGET_LRA_P |
| #define TARGET_LRA_P hook_bool_void_true |
| |
| #undef TARGET_REGISTER_PRIORITY |
| #define TARGET_REGISTER_PRIORITY nds32_register_priority |
| |
| |
| /* Obsolete Macros for Defining Constraints. */ |
| |
| |
| /* Stack Layout and Calling Conventions. */ |
| |
| /* -- Basic Stack Layout. */ |
| |
| /* -- Exception Handling Support. */ |
| |
| /* -- Specifying How Stack Checking is Done. */ |
| |
| /* -- Registers That Address the Stack Frame. */ |
| |
| /* -- Eliminating Frame Pointer and Arg Pointer. */ |
| |
| #undef TARGET_CAN_ELIMINATE |
| #define TARGET_CAN_ELIMINATE nds32_can_eliminate |
| |
| /* -- Passing Function Arguments on the Stack. */ |
| |
| /* -- Passing Arguments in Registers. */ |
| |
| #undef TARGET_FUNCTION_ARG |
| #define TARGET_FUNCTION_ARG nds32_function_arg |
| |
| #undef TARGET_FUNCTION_ARG_ADVANCE |
| #define TARGET_FUNCTION_ARG_ADVANCE nds32_function_arg_advance |
| |
| #undef TARGET_FUNCTION_ARG_BOUNDARY |
| #define TARGET_FUNCTION_ARG_BOUNDARY nds32_function_arg_boundary |
| |
| /* -- How Scalar Function Values Are Returned. */ |
| |
| #undef TARGET_FUNCTION_VALUE |
| #define TARGET_FUNCTION_VALUE nds32_function_value |
| |
| #undef TARGET_LIBCALL_VALUE |
| #define TARGET_LIBCALL_VALUE nds32_libcall_value |
| |
| #undef TARGET_FUNCTION_VALUE_REGNO_P |
| #define TARGET_FUNCTION_VALUE_REGNO_P nds32_function_value_regno_p |
| |
| /* -- How Large Values Are Returned. */ |
| |
| /* -- Caller-Saves Register Allocation. */ |
| |
| /* -- Function Entry and Exit. */ |
| |
| #undef TARGET_ASM_FUNCTION_PROLOGUE |
| #define TARGET_ASM_FUNCTION_PROLOGUE nds32_asm_function_prologue |
| |
| #undef TARGET_ASM_FUNCTION_END_PROLOGUE |
| #define TARGET_ASM_FUNCTION_END_PROLOGUE nds32_asm_function_end_prologue |
| |
| #undef TARGET_ASM_FUNCTION_BEGIN_EPILOGUE |
| #define TARGET_ASM_FUNCTION_BEGIN_EPILOGUE nds32_asm_function_begin_epilogue |
| |
| #undef TARGET_ASM_FUNCTION_EPILOGUE |
| #define TARGET_ASM_FUNCTION_EPILOGUE nds32_asm_function_epilogue |
| |
| #undef TARGET_ASM_OUTPUT_MI_THUNK |
| #define TARGET_ASM_OUTPUT_MI_THUNK nds32_asm_output_mi_thunk |
| |
| #undef TARGET_ASM_CAN_OUTPUT_MI_THUNK |
| #define TARGET_ASM_CAN_OUTPUT_MI_THUNK default_can_output_mi_thunk_no_vcall |
| |
| /* -- Generating Code for Profiling. */ |
| |
| /* -- Permitting tail calls. */ |
| |
| #undef TARGET_WARN_FUNC_RETURN |
| #define TARGET_WARN_FUNC_RETURN nds32_warn_func_return |
| |
| /* Stack smashing protection. */ |
| |
| |
| /* Implementing the Varargs Macros. */ |
| |
| #undef TARGET_STRICT_ARGUMENT_NAMING |
| #define TARGET_STRICT_ARGUMENT_NAMING nds32_strict_argument_naming |
| |
| |
| /* Trampolines for Nested Functions. */ |
| |
| #undef TARGET_ASM_TRAMPOLINE_TEMPLATE |
| #define TARGET_ASM_TRAMPOLINE_TEMPLATE nds32_asm_trampoline_template |
| |
| #undef TARGET_TRAMPOLINE_INIT |
| #define TARGET_TRAMPOLINE_INIT nds32_trampoline_init |
| |
| |
| /* Implicit Calls to Library Routines. */ |
| |
| |
| /* Addressing Modes. */ |
| |
| #undef TARGET_LEGITIMATE_ADDRESS_P |
| #define TARGET_LEGITIMATE_ADDRESS_P nds32_legitimate_address_p |
| |
| |
| /* Anchored Addresses. */ |
| |
| |
| /* Condition Code Status. */ |
| |
| /* -- Representation of condition codes using (cc0). */ |
| |
| /* -- Representation of condition codes using registers. */ |
| |
| /* -- Macros to control conditional execution. */ |
| |
| |
| /* Describing Relative Costs of Operations. */ |
| |
| #undef TARGET_REGISTER_MOVE_COST |
| #define TARGET_REGISTER_MOVE_COST nds32_register_move_cost |
| |
| #undef TARGET_MEMORY_MOVE_COST |
| #define TARGET_MEMORY_MOVE_COST nds32_memory_move_cost |
| |
| #undef TARGET_RTX_COSTS |
| #define TARGET_RTX_COSTS nds32_rtx_costs |
| |
| #undef TARGET_ADDRESS_COST |
| #define TARGET_ADDRESS_COST nds32_address_cost |
| |
| |
| /* Adjusting the Instruction Scheduler. */ |
| |
| |
| /* Dividing the Output into Sections (Texts, Data, . . . ). */ |
| |
| |
| /* Position Independent Code. */ |
| |
| |
| /* Defining the Output Assembler Language. */ |
| |
| /* -- The Overall Framework of an Assembler File. */ |
| |
| #undef TARGET_ASM_FILE_START |
| #define TARGET_ASM_FILE_START nds32_asm_file_start |
| #undef TARGET_ASM_FILE_END |
| #define TARGET_ASM_FILE_END nds32_asm_file_end |
| |
| /* -- Output of Data. */ |
| |
| #undef TARGET_ASM_ALIGNED_HI_OP |
| #define TARGET_ASM_ALIGNED_HI_OP "\t.hword\t" |
| |
| #undef TARGET_ASM_ALIGNED_SI_OP |
| #define TARGET_ASM_ALIGNED_SI_OP "\t.word\t" |
| |
| /* -- Output of Uninitialized Variables. */ |
| |
| /* -- Output and Generation of Labels. */ |
| |
| #undef TARGET_ASM_GLOBALIZE_LABEL |
| #define TARGET_ASM_GLOBALIZE_LABEL nds32_asm_globalize_label |
| |
| /* -- How Initialization Functions Are Handled. */ |
| |
| /* -- Macros Controlling Initialization Routines. */ |
| |
| /* -- Output of Assembler Instructions. */ |
| |
| #undef TARGET_PRINT_OPERAND |
| #define TARGET_PRINT_OPERAND nds32_print_operand |
| #undef TARGET_PRINT_OPERAND_ADDRESS |
| #define TARGET_PRINT_OPERAND_ADDRESS nds32_print_operand_address |
| |
| /* -- Output of Dispatch Tables. */ |
| |
| /* -- Assembler Commands for Exception Regions. */ |
| |
| /* -- Assembler Commands for Alignment. */ |
| |
| |
| /* Controlling Debugging Information Format. */ |
| |
| /* -- Macros Affecting All Debugging Formats. */ |
| |
| /* -- Specific Options for DBX Output. */ |
| |
| /* -- Open-Ended Hooks for DBX Format. */ |
| |
| /* -- File Names in DBX Format. */ |
| |
| /* -- Macros for SDB and DWARF Output. */ |
| |
| /* -- Macros for VMS Debug Format. */ |
| |
| |
| /* Cross Compilation and Floating Point. */ |
| |
| |
| /* Mode Switching Instructions. */ |
| |
| |
| /* Defining target-specific uses of __attribute__. */ |
| |
| #undef TARGET_ATTRIBUTE_TABLE |
| #define TARGET_ATTRIBUTE_TABLE nds32_attribute_table |
| |
| #undef TARGET_MERGE_DECL_ATTRIBUTES |
| #define TARGET_MERGE_DECL_ATTRIBUTES nds32_merge_decl_attributes |
| |
| #undef TARGET_INSERT_ATTRIBUTES |
| #define TARGET_INSERT_ATTRIBUTES nds32_insert_attributes |
| |
| #undef TARGET_OPTION_PRAGMA_PARSE |
| #define TARGET_OPTION_PRAGMA_PARSE nds32_option_pragma_parse |
| |
| #undef TARGET_OPTION_OVERRIDE |
| #define TARGET_OPTION_OVERRIDE nds32_option_override |
| |
| |
| /* Emulating TLS. */ |
| |
| |
| /* Defining coprocessor specifics for MIPS targets. */ |
| |
| |
| /* Parameters for Precompiled Header Validity Checking. */ |
| |
| |
| /* C++ ABI parameters. */ |
| |
| |
| /* Adding support for named address spaces. */ |
| |
| |
| /* Miscellaneous Parameters. */ |
| |
| #undef TARGET_INIT_BUILTINS |
| #define TARGET_INIT_BUILTINS nds32_init_builtins |
| |
| #undef TARGET_EXPAND_BUILTIN |
| #define TARGET_EXPAND_BUILTIN nds32_expand_builtin |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| |
| /* Initialize the GCC target structure. */ |
| |
| struct gcc_target targetm = TARGET_INITIALIZER; |
| |
| /* ------------------------------------------------------------------------ */ |