| /* The Blackfin code generation auxiliary output file. |
| Copyright (C) 2005-2015 Free Software Foundation, Inc. |
| Contributed by Analog Devices. |
| |
| 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 "rtl.h" |
| #include "regs.h" |
| #include "hard-reg-set.h" |
| #include "insn-config.h" |
| #include "insn-codes.h" |
| #include "conditions.h" |
| #include "insn-flags.h" |
| #include "output.h" |
| #include "insn-attr.h" |
| #include "hash-set.h" |
| #include "machmode.h" |
| #include "vec.h" |
| #include "double-int.h" |
| #include "input.h" |
| #include "alias.h" |
| #include "symtab.h" |
| #include "wide-int.h" |
| #include "inchash.h" |
| #include "tree.h" |
| #include "fold-const.h" |
| #include "varasm.h" |
| #include "calls.h" |
| #include "flags.h" |
| #include "except.h" |
| #include "function.h" |
| #include "target.h" |
| #include "target-def.h" |
| #include "hashtab.h" |
| #include "statistics.h" |
| #include "real.h" |
| #include "fixed-value.h" |
| #include "expmed.h" |
| #include "dojump.h" |
| #include "explow.h" |
| #include "emit-rtl.h" |
| #include "stmt.h" |
| #include "expr.h" |
| #include "diagnostic-core.h" |
| #include "recog.h" |
| #include "optabs.h" |
| #include "ggc.h" |
| #include "predict.h" |
| #include "dominance.h" |
| #include "cfg.h" |
| #include "cfgrtl.h" |
| #include "cfganal.h" |
| #include "lcm.h" |
| #include "cfgbuild.h" |
| #include "cfgcleanup.h" |
| #include "basic-block.h" |
| #include "hash-map.h" |
| #include "is-a.h" |
| #include "plugin-api.h" |
| #include "ipa-ref.h" |
| #include "cgraph.h" |
| #include "langhooks.h" |
| #include "bfin-protos.h" |
| #include "tm_p.h" |
| #include "tm-preds.h" |
| #include "tm-constrs.h" |
| #include "gt-bfin.h" |
| #include "timevar.h" |
| #include "df.h" |
| #include "sel-sched.h" |
| #include "hw-doloop.h" |
| #include "opts.h" |
| #include "dumpfile.h" |
| #include "builtins.h" |
| |
| /* A C structure for machine-specific, per-function data. |
| This is added to the cfun structure. */ |
| struct GTY(()) machine_function |
| { |
| /* Set if we are notified by the doloop pass that a hardware loop |
| was created. */ |
| int has_hardware_loops; |
| |
| /* Set if we create a memcpy pattern that uses loop registers. */ |
| int has_loopreg_clobber; |
| }; |
| |
| /* RTX for condition code flag register and RETS register */ |
| extern GTY(()) rtx bfin_cc_rtx; |
| extern GTY(()) rtx bfin_rets_rtx; |
| rtx bfin_cc_rtx, bfin_rets_rtx; |
| |
| int max_arg_registers = 0; |
| |
| /* Arrays used when emitting register names. */ |
| const char *short_reg_names[] = SHORT_REGISTER_NAMES; |
| const char *high_reg_names[] = HIGH_REGISTER_NAMES; |
| const char *dregs_pair_names[] = DREGS_PAIR_NAMES; |
| const char *byte_reg_names[] = BYTE_REGISTER_NAMES; |
| |
| static int arg_regs[] = FUNCTION_ARG_REGISTERS; |
| static int ret_regs[] = FUNCTION_RETURN_REGISTERS; |
| |
| int splitting_for_sched, splitting_loops; |
| |
| static void |
| bfin_globalize_label (FILE *stream, const char *name) |
| { |
| fputs (".global ", stream); |
| assemble_name (stream, name); |
| fputc (';',stream); |
| fputc ('\n',stream); |
| } |
| |
| static void |
| output_file_start (void) |
| { |
| FILE *file = asm_out_file; |
| int i; |
| |
| fprintf (file, ".file \"%s\";\n", LOCATION_FILE (input_location)); |
| |
| for (i = 0; arg_regs[i] >= 0; i++) |
| ; |
| max_arg_registers = i; /* how many arg reg used */ |
| } |
| |
| /* Examine machine-dependent attributes of function type FUNTYPE and return its |
| type. See the definition of E_FUNKIND. */ |
| |
| static e_funkind |
| funkind (const_tree funtype) |
| { |
| tree attrs = TYPE_ATTRIBUTES (funtype); |
| if (lookup_attribute ("interrupt_handler", attrs)) |
| return INTERRUPT_HANDLER; |
| else if (lookup_attribute ("exception_handler", attrs)) |
| return EXCPT_HANDLER; |
| else if (lookup_attribute ("nmi_handler", attrs)) |
| return NMI_HANDLER; |
| else |
| return SUBROUTINE; |
| } |
| |
| /* Legitimize PIC addresses. If the address is already position-independent, |
| we return ORIG. Newly generated position-independent addresses go into a |
| reg. This is REG if nonzero, otherwise we allocate register(s) as |
| necessary. PICREG is the register holding the pointer to the PIC offset |
| table. */ |
| |
| static rtx |
| legitimize_pic_address (rtx orig, rtx reg, rtx picreg) |
| { |
| rtx addr = orig; |
| rtx new_rtx = orig; |
| |
| if (GET_CODE (addr) == SYMBOL_REF || GET_CODE (addr) == LABEL_REF) |
| { |
| int unspec; |
| rtx tmp; |
| |
| if (TARGET_ID_SHARED_LIBRARY) |
| unspec = UNSPEC_MOVE_PIC; |
| else if (GET_CODE (addr) == SYMBOL_REF |
| && SYMBOL_REF_FUNCTION_P (addr)) |
| unspec = UNSPEC_FUNCDESC_GOT17M4; |
| else |
| unspec = UNSPEC_MOVE_FDPIC; |
| |
| if (reg == 0) |
| { |
| gcc_assert (can_create_pseudo_p ()); |
| reg = gen_reg_rtx (Pmode); |
| } |
| |
| tmp = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, addr), unspec); |
| new_rtx = gen_const_mem (Pmode, gen_rtx_PLUS (Pmode, picreg, tmp)); |
| |
| emit_move_insn (reg, new_rtx); |
| if (picreg == pic_offset_table_rtx) |
| crtl->uses_pic_offset_table = 1; |
| return reg; |
| } |
| |
| else if (GET_CODE (addr) == CONST || GET_CODE (addr) == PLUS) |
| { |
| rtx base; |
| |
| if (GET_CODE (addr) == CONST) |
| { |
| addr = XEXP (addr, 0); |
| gcc_assert (GET_CODE (addr) == PLUS); |
| } |
| |
| if (XEXP (addr, 0) == picreg) |
| return orig; |
| |
| if (reg == 0) |
| { |
| gcc_assert (can_create_pseudo_p ()); |
| reg = gen_reg_rtx (Pmode); |
| } |
| |
| base = legitimize_pic_address (XEXP (addr, 0), reg, picreg); |
| addr = legitimize_pic_address (XEXP (addr, 1), |
| base == reg ? NULL_RTX : reg, |
| picreg); |
| |
| if (GET_CODE (addr) == CONST_INT) |
| { |
| gcc_assert (! reload_in_progress && ! reload_completed); |
| addr = force_reg (Pmode, addr); |
| } |
| |
| if (GET_CODE (addr) == PLUS && CONSTANT_P (XEXP (addr, 1))) |
| { |
| base = gen_rtx_PLUS (Pmode, base, XEXP (addr, 0)); |
| addr = XEXP (addr, 1); |
| } |
| |
| return gen_rtx_PLUS (Pmode, base, addr); |
| } |
| |
| return new_rtx; |
| } |
| |
| /* Stack frame layout. */ |
| |
| /* For a given REGNO, determine whether it must be saved in the function |
| prologue. IS_INTHANDLER specifies whether we're generating a normal |
| prologue or an interrupt/exception one. */ |
| static bool |
| must_save_p (bool is_inthandler, unsigned regno) |
| { |
| if (D_REGNO_P (regno)) |
| { |
| bool is_eh_return_reg = false; |
| if (crtl->calls_eh_return) |
| { |
| unsigned j; |
| for (j = 0; ; j++) |
| { |
| unsigned test = EH_RETURN_DATA_REGNO (j); |
| if (test == INVALID_REGNUM) |
| break; |
| if (test == regno) |
| is_eh_return_reg = true; |
| } |
| } |
| |
| return (is_eh_return_reg |
| || (df_regs_ever_live_p (regno) |
| && !fixed_regs[regno] |
| && (is_inthandler || !call_used_regs[regno]))); |
| } |
| else if (P_REGNO_P (regno)) |
| { |
| return ((df_regs_ever_live_p (regno) |
| && !fixed_regs[regno] |
| && (is_inthandler || !call_used_regs[regno])) |
| || (is_inthandler |
| && (ENABLE_WA_05000283 || ENABLE_WA_05000315) |
| && regno == REG_P5) |
| || (!TARGET_FDPIC |
| && regno == PIC_OFFSET_TABLE_REGNUM |
| && (crtl->uses_pic_offset_table |
| || (TARGET_ID_SHARED_LIBRARY && !crtl->is_leaf)))); |
| } |
| else |
| return ((is_inthandler || !call_used_regs[regno]) |
| && (df_regs_ever_live_p (regno) |
| || (!leaf_function_p () && call_used_regs[regno]))); |
| |
| } |
| |
| /* Compute the number of DREGS to save with a push_multiple operation. |
| This could include registers that aren't modified in the function, |
| since push_multiple only takes a range of registers. |
| If IS_INTHANDLER, then everything that is live must be saved, even |
| if normally call-clobbered. |
| If CONSECUTIVE, return the number of registers we can save in one |
| instruction with a push/pop multiple instruction. */ |
| |
| static int |
| n_dregs_to_save (bool is_inthandler, bool consecutive) |
| { |
| int count = 0; |
| unsigned i; |
| |
| for (i = REG_R7 + 1; i-- != REG_R0;) |
| { |
| if (must_save_p (is_inthandler, i)) |
| count++; |
| else if (consecutive) |
| return count; |
| } |
| return count; |
| } |
| |
| /* Like n_dregs_to_save, but compute number of PREGS to save. */ |
| |
| static int |
| n_pregs_to_save (bool is_inthandler, bool consecutive) |
| { |
| int count = 0; |
| unsigned i; |
| |
| for (i = REG_P5 + 1; i-- != REG_P0;) |
| if (must_save_p (is_inthandler, i)) |
| count++; |
| else if (consecutive) |
| return count; |
| return count; |
| } |
| |
| /* Determine if we are going to save the frame pointer in the prologue. */ |
| |
| static bool |
| must_save_fp_p (void) |
| { |
| return df_regs_ever_live_p (REG_FP); |
| } |
| |
| /* Determine if we are going to save the RETS register. */ |
| static bool |
| must_save_rets_p (void) |
| { |
| return df_regs_ever_live_p (REG_RETS); |
| } |
| |
| static bool |
| stack_frame_needed_p (void) |
| { |
| /* EH return puts a new return address into the frame using an |
| address relative to the frame pointer. */ |
| if (crtl->calls_eh_return) |
| return true; |
| return frame_pointer_needed; |
| } |
| |
| /* Emit code to save registers in the prologue. SAVEALL is nonzero if we |
| must save all registers; this is used for interrupt handlers. |
| SPREG contains (reg:SI REG_SP). IS_INTHANDLER is true if we're doing |
| this for an interrupt (or exception) handler. */ |
| |
| static void |
| expand_prologue_reg_save (rtx spreg, int saveall, bool is_inthandler) |
| { |
| rtx predec1 = gen_rtx_PRE_DEC (SImode, spreg); |
| rtx predec = gen_rtx_MEM (SImode, predec1); |
| int ndregs = saveall ? 8 : n_dregs_to_save (is_inthandler, false); |
| int npregs = saveall ? 6 : n_pregs_to_save (is_inthandler, false); |
| int ndregs_consec = saveall ? 8 : n_dregs_to_save (is_inthandler, true); |
| int npregs_consec = saveall ? 6 : n_pregs_to_save (is_inthandler, true); |
| int dregno, pregno; |
| int total_consec = ndregs_consec + npregs_consec; |
| int i, d_to_save; |
| |
| if (saveall || is_inthandler) |
| { |
| rtx_insn *insn = emit_move_insn (predec, gen_rtx_REG (SImode, REG_ASTAT)); |
| |
| RTX_FRAME_RELATED_P (insn) = 1; |
| for (dregno = REG_LT0; dregno <= REG_LB1; dregno++) |
| if (! crtl->is_leaf |
| || cfun->machine->has_hardware_loops |
| || cfun->machine->has_loopreg_clobber |
| || (ENABLE_WA_05000257 |
| && (dregno == REG_LC0 || dregno == REG_LC1))) |
| { |
| insn = emit_move_insn (predec, gen_rtx_REG (SImode, dregno)); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| } |
| } |
| |
| if (total_consec != 0) |
| { |
| rtx_insn *insn; |
| rtx val = GEN_INT (-total_consec * 4); |
| rtx pat = gen_rtx_PARALLEL (VOIDmode, rtvec_alloc (total_consec + 2)); |
| |
| XVECEXP (pat, 0, 0) = gen_rtx_UNSPEC (VOIDmode, gen_rtvec (1, val), |
| UNSPEC_PUSH_MULTIPLE); |
| XVECEXP (pat, 0, total_consec + 1) = gen_rtx_SET (VOIDmode, spreg, |
| gen_rtx_PLUS (Pmode, |
| spreg, |
| val)); |
| RTX_FRAME_RELATED_P (XVECEXP (pat, 0, total_consec + 1)) = 1; |
| d_to_save = ndregs_consec; |
| dregno = REG_R7 + 1 - ndregs_consec; |
| pregno = REG_P5 + 1 - npregs_consec; |
| for (i = 0; i < total_consec; i++) |
| { |
| rtx memref = gen_rtx_MEM (word_mode, |
| gen_rtx_PLUS (Pmode, spreg, |
| GEN_INT (- i * 4 - 4))); |
| rtx subpat; |
| if (d_to_save > 0) |
| { |
| subpat = gen_rtx_SET (VOIDmode, memref, gen_rtx_REG (word_mode, |
| dregno++)); |
| d_to_save--; |
| } |
| else |
| { |
| subpat = gen_rtx_SET (VOIDmode, memref, gen_rtx_REG (word_mode, |
| pregno++)); |
| } |
| XVECEXP (pat, 0, i + 1) = subpat; |
| RTX_FRAME_RELATED_P (subpat) = 1; |
| } |
| insn = emit_insn (pat); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| } |
| |
| for (dregno = REG_R0; ndregs != ndregs_consec; dregno++) |
| { |
| if (must_save_p (is_inthandler, dregno)) |
| { |
| rtx_insn *insn = |
| emit_move_insn (predec, gen_rtx_REG (word_mode, dregno)); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| ndregs--; |
| } |
| } |
| for (pregno = REG_P0; npregs != npregs_consec; pregno++) |
| { |
| if (must_save_p (is_inthandler, pregno)) |
| { |
| rtx_insn *insn = |
| emit_move_insn (predec, gen_rtx_REG (word_mode, pregno)); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| npregs--; |
| } |
| } |
| for (i = REG_P7 + 1; i < REG_CC; i++) |
| if (saveall |
| || (is_inthandler |
| && (df_regs_ever_live_p (i) |
| || (!leaf_function_p () && call_used_regs[i])))) |
| { |
| rtx_insn *insn; |
| if (i == REG_A0 || i == REG_A1) |
| insn = emit_move_insn (gen_rtx_MEM (PDImode, predec1), |
| gen_rtx_REG (PDImode, i)); |
| else |
| insn = emit_move_insn (predec, gen_rtx_REG (SImode, i)); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| } |
| } |
| |
| /* Emit code to restore registers in the epilogue. SAVEALL is nonzero if we |
| must save all registers; this is used for interrupt handlers. |
| SPREG contains (reg:SI REG_SP). IS_INTHANDLER is true if we're doing |
| this for an interrupt (or exception) handler. */ |
| |
| static void |
| expand_epilogue_reg_restore (rtx spreg, bool saveall, bool is_inthandler) |
| { |
| rtx postinc1 = gen_rtx_POST_INC (SImode, spreg); |
| rtx postinc = gen_rtx_MEM (SImode, postinc1); |
| |
| int ndregs = saveall ? 8 : n_dregs_to_save (is_inthandler, false); |
| int npregs = saveall ? 6 : n_pregs_to_save (is_inthandler, false); |
| int ndregs_consec = saveall ? 8 : n_dregs_to_save (is_inthandler, true); |
| int npregs_consec = saveall ? 6 : n_pregs_to_save (is_inthandler, true); |
| int total_consec = ndregs_consec + npregs_consec; |
| int i, regno; |
| rtx_insn *insn; |
| |
| /* A slightly crude technique to stop flow from trying to delete "dead" |
| insns. */ |
| MEM_VOLATILE_P (postinc) = 1; |
| |
| for (i = REG_CC - 1; i > REG_P7; i--) |
| if (saveall |
| || (is_inthandler |
| && (df_regs_ever_live_p (i) |
| || (!leaf_function_p () && call_used_regs[i])))) |
| { |
| if (i == REG_A0 || i == REG_A1) |
| { |
| rtx mem = gen_rtx_MEM (PDImode, postinc1); |
| MEM_VOLATILE_P (mem) = 1; |
| emit_move_insn (gen_rtx_REG (PDImode, i), mem); |
| } |
| else |
| emit_move_insn (gen_rtx_REG (SImode, i), postinc); |
| } |
| |
| regno = REG_P5 - npregs_consec; |
| for (; npregs != npregs_consec; regno--) |
| { |
| if (must_save_p (is_inthandler, regno)) |
| { |
| emit_move_insn (gen_rtx_REG (word_mode, regno), postinc); |
| npregs--; |
| } |
| } |
| regno = REG_R7 - ndregs_consec; |
| for (; ndregs != ndregs_consec; regno--) |
| { |
| if (must_save_p (is_inthandler, regno)) |
| { |
| emit_move_insn (gen_rtx_REG (word_mode, regno), postinc); |
| ndregs--; |
| } |
| } |
| |
| if (total_consec != 0) |
| { |
| rtx pat = gen_rtx_PARALLEL (VOIDmode, rtvec_alloc (total_consec + 1)); |
| XVECEXP (pat, 0, 0) |
| = gen_rtx_SET (VOIDmode, spreg, |
| gen_rtx_PLUS (Pmode, spreg, |
| GEN_INT (total_consec * 4))); |
| |
| if (npregs_consec > 0) |
| regno = REG_P5 + 1; |
| else |
| regno = REG_R7 + 1; |
| |
| for (i = 0; i < total_consec; i++) |
| { |
| rtx addr = (i > 0 |
| ? gen_rtx_PLUS (Pmode, spreg, GEN_INT (i * 4)) |
| : spreg); |
| rtx memref = gen_rtx_MEM (word_mode, addr); |
| |
| regno--; |
| XVECEXP (pat, 0, i + 1) |
| = gen_rtx_SET (VOIDmode, gen_rtx_REG (word_mode, regno), memref); |
| |
| if (npregs_consec > 0) |
| { |
| if (--npregs_consec == 0) |
| regno = REG_R7 + 1; |
| } |
| } |
| |
| insn = emit_insn (pat); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| } |
| if (saveall || is_inthandler) |
| { |
| for (regno = REG_LB1; regno >= REG_LT0; regno--) |
| if (! crtl->is_leaf |
| || cfun->machine->has_hardware_loops |
| || cfun->machine->has_loopreg_clobber |
| || (ENABLE_WA_05000257 && (regno == REG_LC0 || regno == REG_LC1))) |
| emit_move_insn (gen_rtx_REG (SImode, regno), postinc); |
| |
| emit_move_insn (gen_rtx_REG (SImode, REG_ASTAT), postinc); |
| } |
| } |
| |
| /* Perform any needed actions needed for a function that is receiving a |
| variable number of arguments. |
| |
| CUM is as above. |
| |
| MODE and TYPE are the mode and type of the current parameter. |
| |
| PRETEND_SIZE is a variable that should be set to the amount of stack |
| that must be pushed by the prolog to pretend that our caller pushed |
| it. |
| |
| Normally, this macro will push all remaining incoming registers on the |
| stack and set PRETEND_SIZE to the length of the registers pushed. |
| |
| Blackfin specific : |
| - VDSP C compiler manual (our ABI) says that a variable args function |
| should save the R0, R1 and R2 registers in the stack. |
| - The caller will always leave space on the stack for the |
| arguments that are passed in registers, so we dont have |
| to leave any extra space. |
| - now, the vastart pointer can access all arguments from the stack. */ |
| |
| static void |
| setup_incoming_varargs (cumulative_args_t cum, |
| machine_mode mode ATTRIBUTE_UNUSED, |
| tree type ATTRIBUTE_UNUSED, int *pretend_size, |
| int no_rtl) |
| { |
| rtx mem; |
| int i; |
| |
| if (no_rtl) |
| return; |
| |
| /* The move for named arguments will be generated automatically by the |
| compiler. We need to generate the move rtx for the unnamed arguments |
| if they are in the first 3 words. We assume at least 1 named argument |
| exists, so we never generate [ARGP] = R0 here. */ |
| |
| for (i = get_cumulative_args (cum)->words + 1; i < max_arg_registers; i++) |
| { |
| mem = gen_rtx_MEM (Pmode, |
| plus_constant (Pmode, arg_pointer_rtx, |
| (i * UNITS_PER_WORD))); |
| emit_move_insn (mem, gen_rtx_REG (Pmode, i)); |
| } |
| |
| *pretend_size = 0; |
| } |
| |
| /* Value should be nonzero if functions must have frame pointers. |
| Zero means the frame pointer need not be set up (and parms may |
| be accessed via the stack pointer) in functions that seem suitable. */ |
| |
| static bool |
| bfin_frame_pointer_required (void) |
| { |
| e_funkind fkind = funkind (TREE_TYPE (current_function_decl)); |
| |
| if (fkind != SUBROUTINE) |
| return true; |
| |
| /* We turn on -fomit-frame-pointer if -momit-leaf-frame-pointer is used, |
| so we have to override it for non-leaf functions. */ |
| if (TARGET_OMIT_LEAF_FRAME_POINTER && ! crtl->is_leaf) |
| return true; |
| |
| return false; |
| } |
| |
| /* Return the number of registers pushed during the prologue. */ |
| |
| static int |
| n_regs_saved_by_prologue (void) |
| { |
| e_funkind fkind = funkind (TREE_TYPE (current_function_decl)); |
| bool is_inthandler = fkind != SUBROUTINE; |
| tree attrs = TYPE_ATTRIBUTES (TREE_TYPE (current_function_decl)); |
| bool all = (lookup_attribute ("saveall", attrs) != NULL_TREE |
| || (is_inthandler && !crtl->is_leaf)); |
| int ndregs = all ? 8 : n_dregs_to_save (is_inthandler, false); |
| int npregs = all ? 6 : n_pregs_to_save (is_inthandler, false); |
| int n = ndregs + npregs; |
| int i; |
| |
| if (all || stack_frame_needed_p ()) |
| n += 2; |
| else |
| { |
| if (must_save_fp_p ()) |
| n++; |
| if (must_save_rets_p ()) |
| n++; |
| } |
| |
| if (fkind != SUBROUTINE || all) |
| { |
| /* Increment once for ASTAT. */ |
| n++; |
| if (! crtl->is_leaf |
| || cfun->machine->has_hardware_loops |
| || cfun->machine->has_loopreg_clobber) |
| { |
| n += 6; |
| } |
| } |
| |
| if (fkind != SUBROUTINE) |
| { |
| /* RETE/X/N. */ |
| if (lookup_attribute ("nesting", attrs)) |
| n++; |
| } |
| |
| for (i = REG_P7 + 1; i < REG_CC; i++) |
| if (all |
| || (fkind != SUBROUTINE |
| && (df_regs_ever_live_p (i) |
| || (!leaf_function_p () && call_used_regs[i])))) |
| n += i == REG_A0 || i == REG_A1 ? 2 : 1; |
| |
| return n; |
| } |
| |
| /* Given FROM and TO register numbers, say whether this elimination is |
| allowed. Frame pointer elimination is automatically handled. |
| |
| All other eliminations are valid. */ |
| |
| static bool |
| bfin_can_eliminate (const int from ATTRIBUTE_UNUSED, const int to) |
| { |
| return (to == STACK_POINTER_REGNUM ? ! frame_pointer_needed : true); |
| } |
| |
| /* Return the offset between two registers, one to be eliminated, and the other |
| its replacement, at the start of a routine. */ |
| |
| HOST_WIDE_INT |
| bfin_initial_elimination_offset (int from, int to) |
| { |
| HOST_WIDE_INT offset = 0; |
| |
| if (from == ARG_POINTER_REGNUM) |
| offset = n_regs_saved_by_prologue () * 4; |
| |
| if (to == STACK_POINTER_REGNUM) |
| { |
| if (crtl->outgoing_args_size >= FIXED_STACK_AREA) |
| offset += crtl->outgoing_args_size; |
| else if (crtl->outgoing_args_size) |
| offset += FIXED_STACK_AREA; |
| |
| offset += get_frame_size (); |
| } |
| |
| return offset; |
| } |
| |
| /* Emit code to load a constant CONSTANT into register REG; setting |
| RTX_FRAME_RELATED_P on all insns we generate if RELATED is true. |
| Make sure that the insns we generate need not be split. */ |
| |
| static void |
| frame_related_constant_load (rtx reg, HOST_WIDE_INT constant, bool related) |
| { |
| rtx_insn *insn; |
| rtx cst = GEN_INT (constant); |
| |
| if (constant >= -32768 && constant < 65536) |
| insn = emit_move_insn (reg, cst); |
| else |
| { |
| /* We don't call split_load_immediate here, since dwarf2out.c can get |
| confused about some of the more clever sequences it can generate. */ |
| insn = emit_insn (gen_movsi_high (reg, cst)); |
| if (related) |
| RTX_FRAME_RELATED_P (insn) = 1; |
| insn = emit_insn (gen_movsi_low (reg, reg, cst)); |
| } |
| if (related) |
| RTX_FRAME_RELATED_P (insn) = 1; |
| } |
| |
| /* Generate efficient code to add a value to a P register. |
| Set RTX_FRAME_RELATED_P on the generated insns if FRAME is nonzero. |
| EPILOGUE_P is zero if this function is called for prologue, |
| otherwise it's nonzero. And it's less than zero if this is for |
| sibcall epilogue. */ |
| |
| static void |
| add_to_reg (rtx reg, HOST_WIDE_INT value, int frame, int epilogue_p) |
| { |
| if (value == 0) |
| return; |
| |
| /* Choose whether to use a sequence using a temporary register, or |
| a sequence with multiple adds. We can add a signed 7-bit value |
| in one instruction. */ |
| if (value > 120 || value < -120) |
| { |
| rtx tmpreg; |
| rtx tmpreg2; |
| rtx_insn *insn; |
| |
| tmpreg2 = NULL_RTX; |
| |
| /* For prologue or normal epilogue, P1 can be safely used |
| as the temporary register. For sibcall epilogue, we try to find |
| a call used P register, which will be restored in epilogue. |
| If we cannot find such a P register, we have to use one I register |
| to help us. */ |
| |
| if (epilogue_p >= 0) |
| tmpreg = gen_rtx_REG (SImode, REG_P1); |
| else |
| { |
| int i; |
| for (i = REG_P0; i <= REG_P5; i++) |
| if ((df_regs_ever_live_p (i) && ! call_used_regs[i]) |
| || (!TARGET_FDPIC |
| && i == PIC_OFFSET_TABLE_REGNUM |
| && (crtl->uses_pic_offset_table |
| || (TARGET_ID_SHARED_LIBRARY |
| && ! crtl->is_leaf)))) |
| break; |
| if (i <= REG_P5) |
| tmpreg = gen_rtx_REG (SImode, i); |
| else |
| { |
| tmpreg = gen_rtx_REG (SImode, REG_P1); |
| tmpreg2 = gen_rtx_REG (SImode, REG_I0); |
| emit_move_insn (tmpreg2, tmpreg); |
| } |
| } |
| |
| if (frame) |
| frame_related_constant_load (tmpreg, value, TRUE); |
| else |
| insn = emit_move_insn (tmpreg, GEN_INT (value)); |
| |
| insn = emit_insn (gen_addsi3 (reg, reg, tmpreg)); |
| if (frame) |
| RTX_FRAME_RELATED_P (insn) = 1; |
| |
| if (tmpreg2 != NULL_RTX) |
| emit_move_insn (tmpreg, tmpreg2); |
| } |
| else |
| do |
| { |
| int size = value; |
| rtx_insn *insn; |
| |
| if (size > 60) |
| size = 60; |
| else if (size < -60) |
| /* We could use -62, but that would leave the stack unaligned, so |
| it's no good. */ |
| size = -60; |
| |
| insn = emit_insn (gen_addsi3 (reg, reg, GEN_INT (size))); |
| if (frame) |
| RTX_FRAME_RELATED_P (insn) = 1; |
| value -= size; |
| } |
| while (value != 0); |
| } |
| |
| /* Generate a LINK insn for a frame sized FRAME_SIZE. If this constant |
| is too large, generate a sequence of insns that has the same effect. |
| SPREG contains (reg:SI REG_SP). */ |
| |
| static void |
| emit_link_insn (rtx spreg, HOST_WIDE_INT frame_size) |
| { |
| HOST_WIDE_INT link_size = frame_size; |
| rtx_insn *insn; |
| int i; |
| |
| if (link_size > 262140) |
| link_size = 262140; |
| |
| /* Use a LINK insn with as big a constant as possible, then subtract |
| any remaining size from the SP. */ |
| insn = emit_insn (gen_link (GEN_INT (-8 - link_size))); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| |
| for (i = 0; i < XVECLEN (PATTERN (insn), 0); i++) |
| { |
| rtx set = XVECEXP (PATTERN (insn), 0, i); |
| gcc_assert (GET_CODE (set) == SET); |
| RTX_FRAME_RELATED_P (set) = 1; |
| } |
| |
| frame_size -= link_size; |
| |
| if (frame_size > 0) |
| { |
| /* Must use a call-clobbered PREG that isn't the static chain. */ |
| rtx tmpreg = gen_rtx_REG (Pmode, REG_P1); |
| |
| frame_related_constant_load (tmpreg, -frame_size, TRUE); |
| insn = emit_insn (gen_addsi3 (spreg, spreg, tmpreg)); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| } |
| } |
| |
| /* Return the number of bytes we must reserve for outgoing arguments |
| in the current function's stack frame. */ |
| |
| static HOST_WIDE_INT |
| arg_area_size (void) |
| { |
| if (crtl->outgoing_args_size) |
| { |
| if (crtl->outgoing_args_size >= FIXED_STACK_AREA) |
| return crtl->outgoing_args_size; |
| else |
| return FIXED_STACK_AREA; |
| } |
| return 0; |
| } |
| |
| /* Save RETS and FP, and allocate a stack frame. ALL is true if the |
| function must save all its registers (true only for certain interrupt |
| handlers). */ |
| |
| static void |
| do_link (rtx spreg, HOST_WIDE_INT frame_size, bool all) |
| { |
| frame_size += arg_area_size (); |
| |
| if (all |
| || stack_frame_needed_p () |
| || (must_save_rets_p () && must_save_fp_p ())) |
| emit_link_insn (spreg, frame_size); |
| else |
| { |
| if (must_save_rets_p ()) |
| { |
| rtx pat = gen_movsi (gen_rtx_MEM (Pmode, |
| gen_rtx_PRE_DEC (Pmode, spreg)), |
| bfin_rets_rtx); |
| rtx_insn *insn = emit_insn (pat); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| } |
| if (must_save_fp_p ()) |
| { |
| rtx pat = gen_movsi (gen_rtx_MEM (Pmode, |
| gen_rtx_PRE_DEC (Pmode, spreg)), |
| gen_rtx_REG (Pmode, REG_FP)); |
| rtx_insn *insn = emit_insn (pat); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| } |
| add_to_reg (spreg, -frame_size, 1, 0); |
| } |
| } |
| |
| /* Like do_link, but used for epilogues to deallocate the stack frame. |
| EPILOGUE_P is zero if this function is called for prologue, |
| otherwise it's nonzero. And it's less than zero if this is for |
| sibcall epilogue. */ |
| |
| static void |
| do_unlink (rtx spreg, HOST_WIDE_INT frame_size, bool all, int epilogue_p) |
| { |
| frame_size += arg_area_size (); |
| |
| if (stack_frame_needed_p ()) |
| emit_insn (gen_unlink ()); |
| else |
| { |
| rtx postinc = gen_rtx_MEM (Pmode, gen_rtx_POST_INC (Pmode, spreg)); |
| |
| add_to_reg (spreg, frame_size, 0, epilogue_p); |
| if (all || must_save_fp_p ()) |
| { |
| rtx fpreg = gen_rtx_REG (Pmode, REG_FP); |
| emit_move_insn (fpreg, postinc); |
| emit_use (fpreg); |
| } |
| if (all || must_save_rets_p ()) |
| { |
| emit_move_insn (bfin_rets_rtx, postinc); |
| emit_use (bfin_rets_rtx); |
| } |
| } |
| } |
| |
| /* Generate a prologue suitable for a function of kind FKIND. This is |
| called for interrupt and exception handler prologues. |
| SPREG contains (reg:SI REG_SP). */ |
| |
| static void |
| expand_interrupt_handler_prologue (rtx spreg, e_funkind fkind, bool all) |
| { |
| HOST_WIDE_INT frame_size = get_frame_size (); |
| rtx predec1 = gen_rtx_PRE_DEC (SImode, spreg); |
| rtx predec = gen_rtx_MEM (SImode, predec1); |
| rtx_insn *insn; |
| tree attrs = TYPE_ATTRIBUTES (TREE_TYPE (current_function_decl)); |
| tree kspisusp = lookup_attribute ("kspisusp", attrs); |
| |
| if (kspisusp) |
| { |
| insn = emit_move_insn (spreg, gen_rtx_REG (Pmode, REG_USP)); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| } |
| |
| /* We need space on the stack in case we need to save the argument |
| registers. */ |
| if (fkind == EXCPT_HANDLER) |
| { |
| insn = emit_insn (gen_addsi3 (spreg, spreg, GEN_INT (-12))); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| } |
| |
| /* If we're calling other functions, they won't save their call-clobbered |
| registers, so we must save everything here. */ |
| if (!crtl->is_leaf) |
| all = true; |
| expand_prologue_reg_save (spreg, all, true); |
| |
| if (ENABLE_WA_05000283 || ENABLE_WA_05000315) |
| { |
| rtx chipid = GEN_INT (trunc_int_for_mode (0xFFC00014, SImode)); |
| rtx p5reg = gen_rtx_REG (Pmode, REG_P5); |
| emit_insn (gen_movbi (bfin_cc_rtx, const1_rtx)); |
| emit_insn (gen_movsi_high (p5reg, chipid)); |
| emit_insn (gen_movsi_low (p5reg, p5reg, chipid)); |
| emit_insn (gen_dummy_load (p5reg, bfin_cc_rtx)); |
| } |
| |
| if (lookup_attribute ("nesting", attrs)) |
| { |
| rtx srcreg = gen_rtx_REG (Pmode, ret_regs[fkind]); |
| insn = emit_move_insn (predec, srcreg); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| } |
| |
| do_link (spreg, frame_size, all); |
| |
| if (fkind == EXCPT_HANDLER) |
| { |
| rtx r0reg = gen_rtx_REG (SImode, REG_R0); |
| rtx r1reg = gen_rtx_REG (SImode, REG_R1); |
| rtx r2reg = gen_rtx_REG (SImode, REG_R2); |
| |
| emit_move_insn (r0reg, gen_rtx_REG (SImode, REG_SEQSTAT)); |
| emit_insn (gen_ashrsi3 (r0reg, r0reg, GEN_INT (26))); |
| emit_insn (gen_ashlsi3 (r0reg, r0reg, GEN_INT (26))); |
| emit_move_insn (r1reg, spreg); |
| emit_move_insn (r2reg, gen_rtx_REG (Pmode, REG_FP)); |
| emit_insn (gen_addsi3 (r2reg, r2reg, GEN_INT (8))); |
| } |
| } |
| |
| /* Generate an epilogue suitable for a function of kind FKIND. This is |
| called for interrupt and exception handler epilogues. |
| SPREG contains (reg:SI REG_SP). */ |
| |
| static void |
| expand_interrupt_handler_epilogue (rtx spreg, e_funkind fkind, bool all) |
| { |
| tree attrs = TYPE_ATTRIBUTES (TREE_TYPE (current_function_decl)); |
| rtx postinc1 = gen_rtx_POST_INC (SImode, spreg); |
| rtx postinc = gen_rtx_MEM (SImode, postinc1); |
| |
| /* A slightly crude technique to stop flow from trying to delete "dead" |
| insns. */ |
| MEM_VOLATILE_P (postinc) = 1; |
| |
| do_unlink (spreg, get_frame_size (), all, 1); |
| |
| if (lookup_attribute ("nesting", attrs)) |
| { |
| rtx srcreg = gen_rtx_REG (Pmode, ret_regs[fkind]); |
| emit_move_insn (srcreg, postinc); |
| } |
| |
| /* If we're calling other functions, they won't save their call-clobbered |
| registers, so we must save (and restore) everything here. */ |
| if (!crtl->is_leaf) |
| all = true; |
| |
| expand_epilogue_reg_restore (spreg, all, true); |
| |
| /* Deallocate any space we left on the stack in case we needed to save the |
| argument registers. */ |
| if (fkind == EXCPT_HANDLER) |
| emit_insn (gen_addsi3 (spreg, spreg, GEN_INT (12))); |
| |
| emit_jump_insn (gen_return_internal (gen_rtx_REG (Pmode, ret_regs[fkind]))); |
| } |
| |
| /* Used while emitting the prologue to generate code to load the correct value |
| into the PIC register, which is passed in DEST. */ |
| |
| static rtx |
| bfin_load_pic_reg (rtx dest) |
| { |
| struct cgraph_local_info *i = NULL; |
| rtx addr; |
| |
| i = cgraph_node::local_info (current_function_decl); |
| |
| /* Functions local to the translation unit don't need to reload the |
| pic reg, since the caller always passes a usable one. */ |
| if (i && i->local) |
| return pic_offset_table_rtx; |
| |
| if (global_options_set.x_bfin_library_id) |
| addr = plus_constant (Pmode, pic_offset_table_rtx, |
| -4 - bfin_library_id * 4); |
| else |
| addr = gen_rtx_PLUS (Pmode, pic_offset_table_rtx, |
| gen_rtx_UNSPEC (Pmode, gen_rtvec (1, const0_rtx), |
| UNSPEC_LIBRARY_OFFSET)); |
| emit_insn (gen_movsi (dest, gen_rtx_MEM (Pmode, addr))); |
| return dest; |
| } |
| |
| /* Generate RTL for the prologue of the current function. */ |
| |
| void |
| bfin_expand_prologue (void) |
| { |
| HOST_WIDE_INT frame_size = get_frame_size (); |
| rtx spreg = gen_rtx_REG (Pmode, REG_SP); |
| e_funkind fkind = funkind (TREE_TYPE (current_function_decl)); |
| rtx pic_reg_loaded = NULL_RTX; |
| tree attrs = TYPE_ATTRIBUTES (TREE_TYPE (current_function_decl)); |
| bool all = lookup_attribute ("saveall", attrs) != NULL_TREE; |
| |
| if (fkind != SUBROUTINE) |
| { |
| expand_interrupt_handler_prologue (spreg, fkind, all); |
| return; |
| } |
| |
| if (crtl->limit_stack |
| || (TARGET_STACK_CHECK_L1 |
| && !DECL_NO_LIMIT_STACK (current_function_decl))) |
| { |
| HOST_WIDE_INT offset |
| = bfin_initial_elimination_offset (ARG_POINTER_REGNUM, |
| STACK_POINTER_REGNUM); |
| rtx lim = crtl->limit_stack ? stack_limit_rtx : NULL_RTX; |
| rtx tmp = gen_rtx_REG (Pmode, REG_R3); |
| rtx p2reg = gen_rtx_REG (Pmode, REG_P2); |
| |
| emit_move_insn (tmp, p2reg); |
| if (!lim) |
| { |
| emit_move_insn (p2reg, gen_int_mode (0xFFB00000, SImode)); |
| emit_move_insn (p2reg, gen_rtx_MEM (Pmode, p2reg)); |
| lim = p2reg; |
| } |
| if (GET_CODE (lim) == SYMBOL_REF) |
| { |
| if (TARGET_ID_SHARED_LIBRARY) |
| { |
| rtx p1reg = gen_rtx_REG (Pmode, REG_P1); |
| rtx val; |
| pic_reg_loaded = bfin_load_pic_reg (p2reg); |
| val = legitimize_pic_address (stack_limit_rtx, p1reg, |
| pic_reg_loaded); |
| emit_move_insn (p1reg, val); |
| frame_related_constant_load (p2reg, offset, FALSE); |
| emit_insn (gen_addsi3 (p2reg, p2reg, p1reg)); |
| lim = p2reg; |
| } |
| else |
| { |
| rtx limit = plus_constant (Pmode, lim, offset); |
| emit_move_insn (p2reg, limit); |
| lim = p2reg; |
| } |
| } |
| else |
| { |
| if (lim != p2reg) |
| emit_move_insn (p2reg, lim); |
| add_to_reg (p2reg, offset, 0, 0); |
| lim = p2reg; |
| } |
| emit_insn (gen_compare_lt (bfin_cc_rtx, spreg, lim)); |
| emit_insn (gen_trapifcc ()); |
| emit_move_insn (p2reg, tmp); |
| } |
| expand_prologue_reg_save (spreg, all, false); |
| |
| do_link (spreg, frame_size, all); |
| |
| if (TARGET_ID_SHARED_LIBRARY |
| && !TARGET_SEP_DATA |
| && (crtl->uses_pic_offset_table |
| || !crtl->is_leaf)) |
| bfin_load_pic_reg (pic_offset_table_rtx); |
| } |
| |
| /* Generate RTL for the epilogue of the current function. NEED_RETURN is zero |
| if this is for a sibcall. EH_RETURN is nonzero if we're expanding an |
| eh_return pattern. SIBCALL_P is true if this is a sibcall epilogue, |
| false otherwise. */ |
| |
| void |
| bfin_expand_epilogue (int need_return, int eh_return, bool sibcall_p) |
| { |
| rtx spreg = gen_rtx_REG (Pmode, REG_SP); |
| e_funkind fkind = funkind (TREE_TYPE (current_function_decl)); |
| int e = sibcall_p ? -1 : 1; |
| tree attrs = TYPE_ATTRIBUTES (TREE_TYPE (current_function_decl)); |
| bool all = lookup_attribute ("saveall", attrs) != NULL_TREE; |
| |
| if (fkind != SUBROUTINE) |
| { |
| expand_interrupt_handler_epilogue (spreg, fkind, all); |
| return; |
| } |
| |
| do_unlink (spreg, get_frame_size (), all, e); |
| |
| expand_epilogue_reg_restore (spreg, all, false); |
| |
| /* Omit the return insn if this is for a sibcall. */ |
| if (! need_return) |
| return; |
| |
| if (eh_return) |
| emit_insn (gen_addsi3 (spreg, spreg, gen_rtx_REG (Pmode, REG_P2))); |
| |
| emit_jump_insn (gen_return_internal (gen_rtx_REG (Pmode, REG_RETS))); |
| } |
| |
| /* Return nonzero if register OLD_REG can be renamed to register NEW_REG. */ |
| |
| int |
| bfin_hard_regno_rename_ok (unsigned int old_reg ATTRIBUTE_UNUSED, |
| unsigned int new_reg) |
| { |
| /* Interrupt functions can only use registers that have already been |
| saved by the prologue, even if they would normally be |
| call-clobbered. */ |
| |
| if (funkind (TREE_TYPE (current_function_decl)) != SUBROUTINE |
| && !df_regs_ever_live_p (new_reg)) |
| return 0; |
| |
| return 1; |
| } |
| |
| /* Implement TARGET_EXTRA_LIVE_ON_ENTRY. */ |
| static void |
| bfin_extra_live_on_entry (bitmap regs) |
| { |
| if (TARGET_FDPIC) |
| bitmap_set_bit (regs, FDPIC_REGNO); |
| } |
| |
| /* Return the value of the return address for the frame COUNT steps up |
| from the current frame, after the prologue. |
| We punt for everything but the current frame by returning const0_rtx. */ |
| |
| rtx |
| bfin_return_addr_rtx (int count) |
| { |
| if (count != 0) |
| return const0_rtx; |
| |
| return get_hard_reg_initial_val (Pmode, REG_RETS); |
| } |
| |
| static rtx |
| bfin_delegitimize_address (rtx orig_x) |
| { |
| rtx x = orig_x; |
| |
| if (GET_CODE (x) != MEM) |
| return orig_x; |
| |
| x = XEXP (x, 0); |
| if (GET_CODE (x) == PLUS |
| && GET_CODE (XEXP (x, 1)) == UNSPEC |
| && XINT (XEXP (x, 1), 1) == UNSPEC_MOVE_PIC |
| && GET_CODE (XEXP (x, 0)) == REG |
| && REGNO (XEXP (x, 0)) == PIC_OFFSET_TABLE_REGNUM) |
| return XVECEXP (XEXP (x, 1), 0, 0); |
| |
| return orig_x; |
| } |
| |
| /* This predicate is used to compute the length of a load/store insn. |
| OP is a MEM rtx, we return nonzero if its addressing mode requires a |
| 32-bit instruction. */ |
| |
| int |
| effective_address_32bit_p (rtx op, machine_mode mode) |
| { |
| HOST_WIDE_INT offset; |
| |
| mode = GET_MODE (op); |
| op = XEXP (op, 0); |
| |
| if (GET_CODE (op) != PLUS) |
| { |
| gcc_assert (REG_P (op) || GET_CODE (op) == POST_INC |
| || GET_CODE (op) == PRE_DEC || GET_CODE (op) == POST_DEC); |
| return 0; |
| } |
| |
| if (GET_CODE (XEXP (op, 1)) == UNSPEC) |
| return 1; |
| |
| offset = INTVAL (XEXP (op, 1)); |
| |
| /* All byte loads use a 16-bit offset. */ |
| if (GET_MODE_SIZE (mode) == 1) |
| return 1; |
| |
| if (GET_MODE_SIZE (mode) == 4) |
| { |
| /* Frame pointer relative loads can use a negative offset, all others |
| are restricted to a small positive one. */ |
| if (XEXP (op, 0) == frame_pointer_rtx) |
| return offset < -128 || offset > 60; |
| return offset < 0 || offset > 60; |
| } |
| |
| /* Must be HImode now. */ |
| return offset < 0 || offset > 30; |
| } |
| |
| /* Returns true if X is a memory reference using an I register. */ |
| bool |
| bfin_dsp_memref_p (rtx x) |
| { |
| if (! MEM_P (x)) |
| return false; |
| x = XEXP (x, 0); |
| if (GET_CODE (x) == POST_INC || GET_CODE (x) == PRE_INC |
| || GET_CODE (x) == POST_DEC || GET_CODE (x) == PRE_DEC) |
| x = XEXP (x, 0); |
| return IREG_P (x); |
| } |
| |
| /* Return cost of the memory address ADDR. |
| All addressing modes are equally cheap on the Blackfin. */ |
| |
| static int |
| bfin_address_cost (rtx addr ATTRIBUTE_UNUSED, |
| machine_mode mode ATTRIBUTE_UNUSED, |
| addr_space_t as ATTRIBUTE_UNUSED, |
| bool speed ATTRIBUTE_UNUSED) |
| { |
| return 1; |
| } |
| |
| /* Subroutine of print_operand; used to print a memory reference X to FILE. */ |
| |
| void |
| print_address_operand (FILE *file, rtx x) |
| { |
| switch (GET_CODE (x)) |
| { |
| case PLUS: |
| output_address (XEXP (x, 0)); |
| fprintf (file, "+"); |
| output_address (XEXP (x, 1)); |
| break; |
| |
| case PRE_DEC: |
| fprintf (file, "--"); |
| output_address (XEXP (x, 0)); |
| break; |
| case POST_INC: |
| output_address (XEXP (x, 0)); |
| fprintf (file, "++"); |
| break; |
| case POST_DEC: |
| output_address (XEXP (x, 0)); |
| fprintf (file, "--"); |
| break; |
| |
| default: |
| gcc_assert (GET_CODE (x) != MEM); |
| print_operand (file, x, 0); |
| break; |
| } |
| } |
| |
| /* Adding intp DImode support by Tony |
| * -- Q: (low word) |
| * -- R: (high word) |
| */ |
| |
| void |
| print_operand (FILE *file, rtx x, char code) |
| { |
| machine_mode mode; |
| |
| if (code == '!') |
| { |
| if (GET_MODE (current_output_insn) == SImode) |
| fprintf (file, " ||"); |
| else |
| fprintf (file, ";"); |
| return; |
| } |
| |
| mode = GET_MODE (x); |
| |
| switch (code) |
| { |
| case 'j': |
| switch (GET_CODE (x)) |
| { |
| case EQ: |
| fprintf (file, "e"); |
| break; |
| case NE: |
| fprintf (file, "ne"); |
| break; |
| case GT: |
| fprintf (file, "g"); |
| break; |
| case LT: |
| fprintf (file, "l"); |
| break; |
| case GE: |
| fprintf (file, "ge"); |
| break; |
| case LE: |
| fprintf (file, "le"); |
| break; |
| case GTU: |
| fprintf (file, "g"); |
| break; |
| case LTU: |
| fprintf (file, "l"); |
| break; |
| case GEU: |
| fprintf (file, "ge"); |
| break; |
| case LEU: |
| fprintf (file, "le"); |
| break; |
| default: |
| output_operand_lossage ("invalid %%j value"); |
| } |
| break; |
| |
| case 'J': /* reverse logic */ |
| switch (GET_CODE(x)) |
| { |
| case EQ: |
| fprintf (file, "ne"); |
| break; |
| case NE: |
| fprintf (file, "e"); |
| break; |
| case GT: |
| fprintf (file, "le"); |
| break; |
| case LT: |
| fprintf (file, "ge"); |
| break; |
| case GE: |
| fprintf (file, "l"); |
| break; |
| case LE: |
| fprintf (file, "g"); |
| break; |
| case GTU: |
| fprintf (file, "le"); |
| break; |
| case LTU: |
| fprintf (file, "ge"); |
| break; |
| case GEU: |
| fprintf (file, "l"); |
| break; |
| case LEU: |
| fprintf (file, "g"); |
| break; |
| default: |
| output_operand_lossage ("invalid %%J value"); |
| } |
| break; |
| |
| default: |
| switch (GET_CODE (x)) |
| { |
| case REG: |
| if (code == 'h') |
| { |
| if (REGNO (x) < 32) |
| fprintf (file, "%s", short_reg_names[REGNO (x)]); |
| else |
| output_operand_lossage ("invalid operand for code '%c'", code); |
| } |
| else if (code == 'd') |
| { |
| if (REGNO (x) < 32) |
| fprintf (file, "%s", high_reg_names[REGNO (x)]); |
| else |
| output_operand_lossage ("invalid operand for code '%c'", code); |
| } |
| else if (code == 'w') |
| { |
| if (REGNO (x) == REG_A0 || REGNO (x) == REG_A1) |
| fprintf (file, "%s.w", reg_names[REGNO (x)]); |
| else |
| output_operand_lossage ("invalid operand for code '%c'", code); |
| } |
| else if (code == 'x') |
| { |
| if (REGNO (x) == REG_A0 || REGNO (x) == REG_A1) |
| fprintf (file, "%s.x", reg_names[REGNO (x)]); |
| else |
| output_operand_lossage ("invalid operand for code '%c'", code); |
| } |
| else if (code == 'v') |
| { |
| if (REGNO (x) == REG_A0) |
| fprintf (file, "AV0"); |
| else if (REGNO (x) == REG_A1) |
| fprintf (file, "AV1"); |
| else |
| output_operand_lossage ("invalid operand for code '%c'", code); |
| } |
| else if (code == 'D') |
| { |
| if (D_REGNO_P (REGNO (x))) |
| fprintf (file, "%s", dregs_pair_names[REGNO (x)]); |
| else |
| output_operand_lossage ("invalid operand for code '%c'", code); |
| } |
| else if (code == 'H') |
| { |
| if ((mode == DImode || mode == DFmode) && REG_P (x)) |
| fprintf (file, "%s", reg_names[REGNO (x) + 1]); |
| else |
| output_operand_lossage ("invalid operand for code '%c'", code); |
| } |
| else if (code == 'T') |
| { |
| if (D_REGNO_P (REGNO (x))) |
| fprintf (file, "%s", byte_reg_names[REGNO (x)]); |
| else |
| output_operand_lossage ("invalid operand for code '%c'", code); |
| } |
| else |
| fprintf (file, "%s", reg_names[REGNO (x)]); |
| break; |
| |
| case MEM: |
| fputc ('[', file); |
| x = XEXP (x,0); |
| print_address_operand (file, x); |
| fputc (']', file); |
| break; |
| |
| case CONST_INT: |
| if (code == 'M') |
| { |
| switch (INTVAL (x)) |
| { |
| case MACFLAG_NONE: |
| break; |
| case MACFLAG_FU: |
| fputs ("(FU)", file); |
| break; |
| case MACFLAG_T: |
| fputs ("(T)", file); |
| break; |
| case MACFLAG_TFU: |
| fputs ("(TFU)", file); |
| break; |
| case MACFLAG_W32: |
| fputs ("(W32)", file); |
| break; |
| case MACFLAG_IS: |
| fputs ("(IS)", file); |
| break; |
| case MACFLAG_IU: |
| fputs ("(IU)", file); |
| break; |
| case MACFLAG_IH: |
| fputs ("(IH)", file); |
| break; |
| case MACFLAG_M: |
| fputs ("(M)", file); |
| break; |
| case MACFLAG_IS_M: |
| fputs ("(IS,M)", file); |
| break; |
| case MACFLAG_ISS2: |
| fputs ("(ISS2)", file); |
| break; |
| case MACFLAG_S2RND: |
| fputs ("(S2RND)", file); |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| break; |
| } |
| else if (code == 'b') |
| { |
| if (INTVAL (x) == 0) |
| fputs ("+=", file); |
| else if (INTVAL (x) == 1) |
| fputs ("-=", file); |
| else |
| gcc_unreachable (); |
| break; |
| } |
| /* Moves to half registers with d or h modifiers always use unsigned |
| constants. */ |
| else if (code == 'd') |
| x = GEN_INT ((INTVAL (x) >> 16) & 0xffff); |
| else if (code == 'h') |
| x = GEN_INT (INTVAL (x) & 0xffff); |
| else if (code == 'N') |
| x = GEN_INT (-INTVAL (x)); |
| else if (code == 'X') |
| x = GEN_INT (exact_log2 (0xffffffff & INTVAL (x))); |
| else if (code == 'Y') |
| x = GEN_INT (exact_log2 (0xffffffff & ~INTVAL (x))); |
| else if (code == 'Z') |
| /* Used for LINK insns. */ |
| x = GEN_INT (-8 - INTVAL (x)); |
| |
| /* fall through */ |
| |
| case SYMBOL_REF: |
| output_addr_const (file, x); |
| break; |
| |
| case CONST_DOUBLE: |
| output_operand_lossage ("invalid const_double operand"); |
| break; |
| |
| case UNSPEC: |
| switch (XINT (x, 1)) |
| { |
| case UNSPEC_MOVE_PIC: |
| output_addr_const (file, XVECEXP (x, 0, 0)); |
| fprintf (file, "@GOT"); |
| break; |
| |
| case UNSPEC_MOVE_FDPIC: |
| output_addr_const (file, XVECEXP (x, 0, 0)); |
| fprintf (file, "@GOT17M4"); |
| break; |
| |
| case UNSPEC_FUNCDESC_GOT17M4: |
| output_addr_const (file, XVECEXP (x, 0, 0)); |
| fprintf (file, "@FUNCDESC_GOT17M4"); |
| break; |
| |
| case UNSPEC_LIBRARY_OFFSET: |
| fprintf (file, "_current_shared_library_p5_offset_"); |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| break; |
| |
| default: |
| output_addr_const (file, x); |
| } |
| } |
| } |
| |
| /* Argument support functions. */ |
| |
| /* Initialize a variable CUM of type CUMULATIVE_ARGS |
| for a call to a function whose data type is FNTYPE. |
| For a library call, FNTYPE is 0. |
| VDSP C Compiler manual, our ABI says that |
| first 3 words of arguments will use R0, R1 and R2. |
| */ |
| |
| void |
| init_cumulative_args (CUMULATIVE_ARGS *cum, tree fntype, |
| rtx libname ATTRIBUTE_UNUSED) |
| { |
| static CUMULATIVE_ARGS zero_cum; |
| |
| *cum = zero_cum; |
| |
| /* Set up the number of registers to use for passing arguments. */ |
| |
| cum->nregs = max_arg_registers; |
| cum->arg_regs = arg_regs; |
| |
| cum->call_cookie = CALL_NORMAL; |
| /* Check for a longcall attribute. */ |
| if (fntype && lookup_attribute ("shortcall", TYPE_ATTRIBUTES (fntype))) |
| cum->call_cookie |= CALL_SHORT; |
| else if (fntype && lookup_attribute ("longcall", TYPE_ATTRIBUTES (fntype))) |
| cum->call_cookie |= CALL_LONG; |
| |
| return; |
| } |
| |
| /* Update the data in CUM to advance over an argument |
| of mode MODE and data type TYPE. |
| (TYPE is null for libcalls where that information may not be available.) */ |
| |
| static void |
| bfin_function_arg_advance (cumulative_args_t cum_v, machine_mode mode, |
| const_tree type, bool named ATTRIBUTE_UNUSED) |
| { |
| CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v); |
| int count, bytes, words; |
| |
| bytes = (mode == BLKmode) ? int_size_in_bytes (type) : GET_MODE_SIZE (mode); |
| words = (bytes + UNITS_PER_WORD - 1) / UNITS_PER_WORD; |
| |
| cum->words += words; |
| cum->nregs -= words; |
| |
| if (cum->nregs <= 0) |
| { |
| cum->nregs = 0; |
| cum->arg_regs = NULL; |
| } |
| else |
| { |
| for (count = 1; count <= words; count++) |
| cum->arg_regs++; |
| } |
| |
| return; |
| } |
| |
| /* Define where to put the arguments to a function. |
| Value is zero to push the argument on the stack, |
| or a hard register in which to store the argument. |
| |
| MODE is the argument's machine mode. |
| TYPE is the data type of the argument (as a tree). |
| This is null for libcalls where that information may |
| not be available. |
| CUM is a variable of type CUMULATIVE_ARGS which gives info about |
| the preceding args and about the function being called. |
| NAMED is nonzero if this argument is a named parameter |
| (otherwise it is an extra parameter matching an ellipsis). */ |
| |
| static rtx |
| bfin_function_arg (cumulative_args_t cum_v, machine_mode mode, |
| const_tree type, bool named ATTRIBUTE_UNUSED) |
| { |
| CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v); |
| int bytes |
| = (mode == BLKmode) ? int_size_in_bytes (type) : GET_MODE_SIZE (mode); |
| |
| if (mode == VOIDmode) |
| /* Compute operand 2 of the call insn. */ |
| return GEN_INT (cum->call_cookie); |
| |
| if (bytes == -1) |
| return NULL_RTX; |
| |
| if (cum->nregs) |
| return gen_rtx_REG (mode, *(cum->arg_regs)); |
| |
| return NULL_RTX; |
| } |
| |
| /* For an arg passed partly in registers and partly in memory, |
| this is the number of bytes passed in registers. |
| For args passed entirely in registers or entirely in memory, zero. |
| |
| Refer VDSP C Compiler manual, our ABI. |
| First 3 words are in registers. So, if an argument is larger |
| than the registers available, it will span the register and |
| stack. */ |
| |
| static int |
| bfin_arg_partial_bytes (cumulative_args_t cum, machine_mode mode, |
| tree type ATTRIBUTE_UNUSED, |
| bool named ATTRIBUTE_UNUSED) |
| { |
| int bytes |
| = (mode == BLKmode) ? int_size_in_bytes (type) : GET_MODE_SIZE (mode); |
| int bytes_left = get_cumulative_args (cum)->nregs * UNITS_PER_WORD; |
| |
| if (bytes == -1) |
| return 0; |
| |
| if (bytes_left == 0) |
| return 0; |
| if (bytes > bytes_left) |
| return bytes_left; |
| return 0; |
| } |
| |
| /* Variable sized types are passed by reference. */ |
| |
| static bool |
| bfin_pass_by_reference (cumulative_args_t cum ATTRIBUTE_UNUSED, |
| machine_mode mode ATTRIBUTE_UNUSED, |
| const_tree type, bool named ATTRIBUTE_UNUSED) |
| { |
| return type && TREE_CODE (TYPE_SIZE (type)) != INTEGER_CST; |
| } |
| |
| /* Decide whether a type should be returned in memory (true) |
| or in a register (false). This is called by the macro |
| TARGET_RETURN_IN_MEMORY. */ |
| |
| static bool |
| bfin_return_in_memory (const_tree type, const_tree fntype ATTRIBUTE_UNUSED) |
| { |
| int size = int_size_in_bytes (type); |
| return size > 2 * UNITS_PER_WORD || size == -1; |
| } |
| |
| /* Register in which address to store a structure value |
| is passed to a function. */ |
| static rtx |
| bfin_struct_value_rtx (tree fntype ATTRIBUTE_UNUSED, |
| int incoming ATTRIBUTE_UNUSED) |
| { |
| return gen_rtx_REG (Pmode, REG_P0); |
| } |
| |
| /* Return true when register may be used to pass function parameters. */ |
| |
| bool |
| function_arg_regno_p (int n) |
| { |
| int i; |
| for (i = 0; arg_regs[i] != -1; i++) |
| if (n == arg_regs[i]) |
| return true; |
| return false; |
| } |
| |
| /* Returns 1 if OP contains a symbol reference */ |
| |
| int |
| symbolic_reference_mentioned_p (rtx op) |
| { |
| register const char *fmt; |
| register int i; |
| |
| if (GET_CODE (op) == SYMBOL_REF || GET_CODE (op) == LABEL_REF) |
| return 1; |
| |
| fmt = GET_RTX_FORMAT (GET_CODE (op)); |
| for (i = GET_RTX_LENGTH (GET_CODE (op)) - 1; i >= 0; i--) |
| { |
| if (fmt[i] == 'E') |
| { |
| register int j; |
| |
| for (j = XVECLEN (op, i) - 1; j >= 0; j--) |
| if (symbolic_reference_mentioned_p (XVECEXP (op, i, j))) |
| return 1; |
| } |
| |
| else if (fmt[i] == 'e' && symbolic_reference_mentioned_p (XEXP (op, i))) |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /* Decide whether we can make a sibling call to a function. DECL is the |
| declaration of the function being targeted by the call and EXP is the |
| CALL_EXPR representing the call. */ |
| |
| static bool |
| bfin_function_ok_for_sibcall (tree decl ATTRIBUTE_UNUSED, |
| tree exp ATTRIBUTE_UNUSED) |
| { |
| struct cgraph_local_info *this_func, *called_func; |
| e_funkind fkind = funkind (TREE_TYPE (current_function_decl)); |
| if (fkind != SUBROUTINE) |
| return false; |
| if (!TARGET_ID_SHARED_LIBRARY || TARGET_SEP_DATA) |
| return true; |
| |
| /* When compiling for ID shared libraries, can't sibcall a local function |
| from a non-local function, because the local function thinks it does |
| not need to reload P5 in the prologue, but the sibcall wil pop P5 in the |
| sibcall epilogue, and we end up with the wrong value in P5. */ |
| |
| if (!decl) |
| /* Not enough information. */ |
| return false; |
| |
| this_func = cgraph_node::local_info (current_function_decl); |
| called_func = cgraph_node::local_info (decl); |
| if (!called_func) |
| return false; |
| return !called_func->local || this_func->local; |
| } |
| |
| /* Write a template for a trampoline to F. */ |
| |
| static void |
| bfin_asm_trampoline_template (FILE *f) |
| { |
| if (TARGET_FDPIC) |
| { |
| fprintf (f, "\t.dd\t0x00000000\n"); /* 0 */ |
| fprintf (f, "\t.dd\t0x00000000\n"); /* 0 */ |
| fprintf (f, "\t.dd\t0x0000e109\n"); /* p1.l = fn low */ |
| fprintf (f, "\t.dd\t0x0000e149\n"); /* p1.h = fn high */ |
| fprintf (f, "\t.dd\t0x0000e10a\n"); /* p2.l = sc low */ |
| fprintf (f, "\t.dd\t0x0000e14a\n"); /* p2.h = sc high */ |
| fprintf (f, "\t.dw\t0xac4b\n"); /* p3 = [p1 + 4] */ |
| fprintf (f, "\t.dw\t0x9149\n"); /* p1 = [p1] */ |
| fprintf (f, "\t.dw\t0x0051\n"); /* jump (p1)*/ |
| } |
| else |
| { |
| fprintf (f, "\t.dd\t0x0000e109\n"); /* p1.l = fn low */ |
| fprintf (f, "\t.dd\t0x0000e149\n"); /* p1.h = fn high */ |
| fprintf (f, "\t.dd\t0x0000e10a\n"); /* p2.l = sc low */ |
| fprintf (f, "\t.dd\t0x0000e14a\n"); /* p2.h = sc high */ |
| fprintf (f, "\t.dw\t0x0051\n"); /* jump (p1)*/ |
| } |
| } |
| |
| /* Emit RTL insns to initialize the variable parts of a trampoline at |
| M_TRAMP. FNDECL is the target function. CHAIN_VALUE is an RTX for |
| the static chain value for the function. */ |
| |
| static void |
| bfin_trampoline_init (rtx m_tramp, tree fndecl, rtx chain_value) |
| { |
| rtx t1 = copy_to_reg (XEXP (DECL_RTL (fndecl), 0)); |
| rtx t2 = copy_to_reg (chain_value); |
| rtx mem; |
| int i = 0; |
| |
| emit_block_move (m_tramp, assemble_trampoline_template (), |
| GEN_INT (TRAMPOLINE_SIZE), BLOCK_OP_NORMAL); |
| |
| if (TARGET_FDPIC) |
| { |
| rtx a = force_reg (Pmode, plus_constant (Pmode, XEXP (m_tramp, 0), 8)); |
| mem = adjust_address (m_tramp, Pmode, 0); |
| emit_move_insn (mem, a); |
| i = 8; |
| } |
| |
| mem = adjust_address (m_tramp, HImode, i + 2); |
| emit_move_insn (mem, gen_lowpart (HImode, t1)); |
| emit_insn (gen_ashrsi3 (t1, t1, GEN_INT (16))); |
| mem = adjust_address (m_tramp, HImode, i + 6); |
| emit_move_insn (mem, gen_lowpart (HImode, t1)); |
| |
| mem = adjust_address (m_tramp, HImode, i + 10); |
| emit_move_insn (mem, gen_lowpart (HImode, t2)); |
| emit_insn (gen_ashrsi3 (t2, t2, GEN_INT (16))); |
| mem = adjust_address (m_tramp, HImode, i + 14); |
| emit_move_insn (mem, gen_lowpart (HImode, t2)); |
| } |
| |
| /* Emit insns to move operands[1] into operands[0]. */ |
| |
| void |
| emit_pic_move (rtx *operands, machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| rtx temp = reload_in_progress ? operands[0] : gen_reg_rtx (Pmode); |
| |
| gcc_assert (!TARGET_FDPIC || !(reload_in_progress || reload_completed)); |
| if (GET_CODE (operands[0]) == MEM && SYMBOLIC_CONST (operands[1])) |
| operands[1] = force_reg (SImode, operands[1]); |
| else |
| operands[1] = legitimize_pic_address (operands[1], temp, |
| TARGET_FDPIC ? OUR_FDPIC_REG |
| : pic_offset_table_rtx); |
| } |
| |
| /* Expand a move operation in mode MODE. The operands are in OPERANDS. |
| Returns true if no further code must be generated, false if the caller |
| should generate an insn to move OPERANDS[1] to OPERANDS[0]. */ |
| |
| bool |
| expand_move (rtx *operands, machine_mode mode) |
| { |
| rtx op = operands[1]; |
| if ((TARGET_ID_SHARED_LIBRARY || TARGET_FDPIC) |
| && SYMBOLIC_CONST (op)) |
| emit_pic_move (operands, mode); |
| else if (mode == SImode && GET_CODE (op) == CONST |
| && GET_CODE (XEXP (op, 0)) == PLUS |
| && GET_CODE (XEXP (XEXP (op, 0), 0)) == SYMBOL_REF |
| && !targetm.legitimate_constant_p (mode, op)) |
| { |
| rtx dest = operands[0]; |
| rtx op0, op1; |
| gcc_assert (!reload_in_progress && !reload_completed); |
| op = XEXP (op, 0); |
| op0 = force_reg (mode, XEXP (op, 0)); |
| op1 = XEXP (op, 1); |
| if (!insn_data[CODE_FOR_addsi3].operand[2].predicate (op1, mode)) |
| op1 = force_reg (mode, op1); |
| if (GET_CODE (dest) == MEM) |
| dest = gen_reg_rtx (mode); |
| emit_insn (gen_addsi3 (dest, op0, op1)); |
| if (dest == operands[0]) |
| return true; |
| operands[1] = dest; |
| } |
| /* Don't generate memory->memory or constant->memory moves, go through a |
| register */ |
| else if ((reload_in_progress | reload_completed) == 0 |
| && GET_CODE (operands[0]) == MEM |
| && GET_CODE (operands[1]) != REG) |
| operands[1] = force_reg (mode, operands[1]); |
| return false; |
| } |
| |
| /* Split one or more DImode RTL references into pairs of SImode |
| references. The RTL can be REG, offsettable MEM, integer constant, or |
| CONST_DOUBLE. "operands" is a pointer to an array of DImode RTL to |
| split and "num" is its length. lo_half and hi_half are output arrays |
| that parallel "operands". */ |
| |
| void |
| split_di (rtx operands[], int num, rtx lo_half[], rtx hi_half[]) |
| { |
| while (num--) |
| { |
| rtx op = operands[num]; |
| |
| /* simplify_subreg refuse to split volatile memory addresses, |
| but we still have to handle it. */ |
| if (GET_CODE (op) == MEM) |
| { |
| lo_half[num] = adjust_address (op, SImode, 0); |
| hi_half[num] = adjust_address (op, SImode, 4); |
| } |
| else |
| { |
| lo_half[num] = simplify_gen_subreg (SImode, op, |
| GET_MODE (op) == VOIDmode |
| ? DImode : GET_MODE (op), 0); |
| hi_half[num] = simplify_gen_subreg (SImode, op, |
| GET_MODE (op) == VOIDmode |
| ? DImode : GET_MODE (op), 4); |
| } |
| } |
| } |
| |
| bool |
| bfin_longcall_p (rtx op, int call_cookie) |
| { |
| gcc_assert (GET_CODE (op) == SYMBOL_REF); |
| if (SYMBOL_REF_WEAK (op)) |
| return 1; |
| if (call_cookie & CALL_SHORT) |
| return 0; |
| if (call_cookie & CALL_LONG) |
| return 1; |
| if (TARGET_LONG_CALLS) |
| return 1; |
| return 0; |
| } |
| |
| /* Expand a call instruction. FNADDR is the call target, RETVAL the return value. |
| COOKIE is a CONST_INT holding the call_cookie prepared init_cumulative_args. |
| SIBCALL is nonzero if this is a sibling call. */ |
| |
| void |
| bfin_expand_call (rtx retval, rtx fnaddr, rtx callarg1, rtx cookie, int sibcall) |
| { |
| rtx use = NULL, call; |
| rtx callee = XEXP (fnaddr, 0); |
| int nelts = 3; |
| rtx pat; |
| rtx picreg = get_hard_reg_initial_val (SImode, FDPIC_REGNO); |
| rtx retsreg = gen_rtx_REG (Pmode, REG_RETS); |
| int n; |
| |
| /* In an untyped call, we can get NULL for operand 2. */ |
| if (cookie == NULL_RTX) |
| cookie = const0_rtx; |
| |
| /* Static functions and indirect calls don't need the pic register. */ |
| if (!TARGET_FDPIC && flag_pic |
| && GET_CODE (callee) == SYMBOL_REF |
| && !SYMBOL_REF_LOCAL_P (callee)) |
| use_reg (&use, pic_offset_table_rtx); |
| |
| if (TARGET_FDPIC) |
| { |
| int caller_in_sram, callee_in_sram; |
| |
| /* 0 is not in sram, 1 is in L1 sram, 2 is in L2 sram. */ |
| caller_in_sram = callee_in_sram = 0; |
| |
| if (lookup_attribute ("l1_text", |
| DECL_ATTRIBUTES (cfun->decl)) != NULL_TREE) |
| caller_in_sram = 1; |
| else if (lookup_attribute ("l2", |
| DECL_ATTRIBUTES (cfun->decl)) != NULL_TREE) |
| caller_in_sram = 2; |
| |
| if (GET_CODE (callee) == SYMBOL_REF |
| && SYMBOL_REF_DECL (callee) && DECL_P (SYMBOL_REF_DECL (callee))) |
| { |
| if (lookup_attribute |
| ("l1_text", |
| DECL_ATTRIBUTES (SYMBOL_REF_DECL (callee))) != NULL_TREE) |
| callee_in_sram = 1; |
| else if (lookup_attribute |
| ("l2", |
| DECL_ATTRIBUTES (SYMBOL_REF_DECL (callee))) != NULL_TREE) |
| callee_in_sram = 2; |
| } |
| |
| if (GET_CODE (callee) != SYMBOL_REF |
| || bfin_longcall_p (callee, INTVAL (cookie)) |
| || (GET_CODE (callee) == SYMBOL_REF |
| && !SYMBOL_REF_LOCAL_P (callee) |
| && TARGET_INLINE_PLT) |
| || caller_in_sram != callee_in_sram |
| || (caller_in_sram && callee_in_sram |
| && (GET_CODE (callee) != SYMBOL_REF |
| || !SYMBOL_REF_LOCAL_P (callee)))) |
| { |
| rtx addr = callee; |
| if (! address_operand (addr, Pmode)) |
| addr = force_reg (Pmode, addr); |
| |
| fnaddr = gen_reg_rtx (SImode); |
| emit_insn (gen_load_funcdescsi (fnaddr, addr)); |
| fnaddr = gen_rtx_MEM (Pmode, fnaddr); |
| |
| picreg = gen_reg_rtx (SImode); |
| emit_insn (gen_load_funcdescsi (picreg, |
| plus_constant (Pmode, addr, 4))); |
| } |
| |
| nelts++; |
| } |
| else if ((!register_no_elim_operand (callee, Pmode) |
| && GET_CODE (callee) != SYMBOL_REF) |
| || (GET_CODE (callee) == SYMBOL_REF |
| && ((TARGET_ID_SHARED_LIBRARY && !TARGET_LEAF_ID_SHARED_LIBRARY) |
| || bfin_longcall_p (callee, INTVAL (cookie))))) |
| { |
| callee = copy_to_mode_reg (Pmode, callee); |
| fnaddr = gen_rtx_MEM (Pmode, callee); |
| } |
| call = gen_rtx_CALL (VOIDmode, fnaddr, callarg1); |
| |
| if (retval) |
| call = gen_rtx_SET (VOIDmode, retval, call); |
| |
| pat = gen_rtx_PARALLEL (VOIDmode, rtvec_alloc (nelts)); |
| n = 0; |
| XVECEXP (pat, 0, n++) = call; |
| if (TARGET_FDPIC) |
| XVECEXP (pat, 0, n++) = gen_rtx_USE (VOIDmode, picreg); |
| XVECEXP (pat, 0, n++) = gen_rtx_USE (VOIDmode, cookie); |
| if (sibcall) |
| XVECEXP (pat, 0, n++) = ret_rtx; |
| else |
| XVECEXP (pat, 0, n++) = gen_rtx_CLOBBER (VOIDmode, retsreg); |
| call = emit_call_insn (pat); |
| if (use) |
| CALL_INSN_FUNCTION_USAGE (call) = use; |
| } |
| |
| /* Return 1 if hard register REGNO can hold a value of machine-mode MODE. */ |
| |
| int |
| hard_regno_mode_ok (int regno, machine_mode mode) |
| { |
| /* Allow only dregs to store value of mode HI or QI */ |
| enum reg_class rclass = REGNO_REG_CLASS (regno); |
| |
| if (mode == CCmode) |
| return 0; |
| |
| if (mode == V2HImode) |
| return D_REGNO_P (regno); |
| if (rclass == CCREGS) |
| return mode == BImode; |
| if (mode == PDImode || mode == V2PDImode) |
| return regno == REG_A0 || regno == REG_A1; |
| |
| /* Allow all normal 32-bit regs, except REG_M3, in case regclass ever comes |
| up with a bad register class (such as ALL_REGS) for DImode. */ |
| if (mode == DImode) |
| return regno < REG_M3; |
| |
| if (mode == SImode |
| && TEST_HARD_REG_BIT (reg_class_contents[PROLOGUE_REGS], regno)) |
| return 1; |
| |
| return TEST_HARD_REG_BIT (reg_class_contents[MOST_REGS], regno); |
| } |
| |
| /* Implements target hook vector_mode_supported_p. */ |
| |
| static bool |
| bfin_vector_mode_supported_p (machine_mode mode) |
| { |
| return mode == V2HImode; |
| } |
| |
| /* Worker function for TARGET_REGISTER_MOVE_COST. */ |
| |
| static int |
| bfin_register_move_cost (machine_mode mode, |
| reg_class_t class1, reg_class_t class2) |
| { |
| /* These need secondary reloads, so they're more expensive. */ |
| if ((class1 == CCREGS && !reg_class_subset_p (class2, DREGS)) |
| || (class2 == CCREGS && !reg_class_subset_p (class1, DREGS))) |
| return 4; |
| |
| /* If optimizing for size, always prefer reg-reg over reg-memory moves. */ |
| if (optimize_size) |
| return 2; |
| |
| if (GET_MODE_CLASS (mode) == MODE_INT) |
| { |
| /* Discourage trying to use the accumulators. */ |
| if (TEST_HARD_REG_BIT (reg_class_contents[class1], REG_A0) |
| || TEST_HARD_REG_BIT (reg_class_contents[class1], REG_A1) |
| || TEST_HARD_REG_BIT (reg_class_contents[class2], REG_A0) |
| || TEST_HARD_REG_BIT (reg_class_contents[class2], REG_A1)) |
| return 20; |
| } |
| return 2; |
| } |
| |
| /* Worker function for TARGET_MEMORY_MOVE_COST. |
| |
| ??? In theory L1 memory has single-cycle latency. We should add a switch |
| that tells the compiler whether we expect to use only L1 memory for the |
| program; it'll make the costs more accurate. */ |
| |
| static int |
| bfin_memory_move_cost (machine_mode mode ATTRIBUTE_UNUSED, |
| reg_class_t rclass, |
| bool in ATTRIBUTE_UNUSED) |
| { |
| /* Make memory accesses slightly more expensive than any register-register |
| move. Also, penalize non-DP registers, since they need secondary |
| reloads to load and store. */ |
| if (! reg_class_subset_p (rclass, DPREGS)) |
| return 10; |
| |
| return 8; |
| } |
| |
| /* Inform reload about cases where moving X with a mode MODE to a register in |
| RCLASS requires an extra scratch register. Return the class needed for the |
| scratch register. */ |
| |
| static reg_class_t |
| bfin_secondary_reload (bool in_p, rtx x, reg_class_t rclass_i, |
| machine_mode mode, secondary_reload_info *sri) |
| { |
| /* If we have HImode or QImode, we can only use DREGS as secondary registers; |
| in most other cases we can also use PREGS. */ |
| enum reg_class default_class = GET_MODE_SIZE (mode) >= 4 ? DPREGS : DREGS; |
| enum reg_class x_class = NO_REGS; |
| enum rtx_code code = GET_CODE (x); |
| enum reg_class rclass = (enum reg_class) rclass_i; |
| |
| if (code == SUBREG) |
| x = SUBREG_REG (x), code = GET_CODE (x); |
| if (REG_P (x)) |
| { |
| int regno = REGNO (x); |
| if (regno >= FIRST_PSEUDO_REGISTER) |
| regno = reg_renumber[regno]; |
| |
| if (regno == -1) |
| code = MEM; |
| else |
| x_class = REGNO_REG_CLASS (regno); |
| } |
| |
| /* We can be asked to reload (plus (FP) (large_constant)) into a DREG. |
| This happens as a side effect of register elimination, and we need |
| a scratch register to do it. */ |
| if (fp_plus_const_operand (x, mode)) |
| { |
| rtx op2 = XEXP (x, 1); |
| int large_constant_p = ! satisfies_constraint_Ks7 (op2); |
| |
| if (rclass == PREGS || rclass == PREGS_CLOBBERED) |
| return NO_REGS; |
| /* If destination is a DREG, we can do this without a scratch register |
| if the constant is valid for an add instruction. */ |
| if ((rclass == DREGS || rclass == DPREGS) |
| && ! large_constant_p) |
| return NO_REGS; |
| /* Reloading to anything other than a DREG? Use a PREG scratch |
| register. */ |
| sri->icode = CODE_FOR_reload_insi; |
| return NO_REGS; |
| } |
| |
| /* Data can usually be moved freely between registers of most classes. |
| AREGS are an exception; they can only move to or from another register |
| in AREGS or one in DREGS. They can also be assigned the constant 0. */ |
| if (x_class == AREGS || x_class == EVEN_AREGS || x_class == ODD_AREGS) |
| return (rclass == DREGS || rclass == AREGS || rclass == EVEN_AREGS |
| || rclass == ODD_AREGS |
| ? NO_REGS : DREGS); |
| |
| if (rclass == AREGS || rclass == EVEN_AREGS || rclass == ODD_AREGS) |
| { |
| if (code == MEM) |
| { |
| sri->icode = in_p ? CODE_FOR_reload_inpdi : CODE_FOR_reload_outpdi; |
| return NO_REGS; |
| } |
| |
| if (x != const0_rtx && x_class != DREGS) |
| { |
| return DREGS; |
| } |
| else |
| return NO_REGS; |
| } |
| |
| /* CCREGS can only be moved from/to DREGS. */ |
| if (rclass == CCREGS && x_class != DREGS) |
| return DREGS; |
| if (x_class == CCREGS && rclass != DREGS) |
| return DREGS; |
| |
| /* All registers other than AREGS can load arbitrary constants. The only |
| case that remains is MEM. */ |
| if (code == MEM) |
| if (! reg_class_subset_p (rclass, default_class)) |
| return default_class; |
| |
| return NO_REGS; |
| } |
| |
| /* Implement TARGET_CLASS_LIKELY_SPILLED_P. */ |
| |
| static bool |
| bfin_class_likely_spilled_p (reg_class_t rclass) |
| { |
| switch (rclass) |
| { |
| case PREGS_CLOBBERED: |
| case PROLOGUE_REGS: |
| case P0REGS: |
| case D0REGS: |
| case D1REGS: |
| case D2REGS: |
| case CCREGS: |
| return true; |
| |
| default: |
| break; |
| } |
| |
| return false; |
| } |
| |
| static struct machine_function * |
| bfin_init_machine_status (void) |
| { |
| return ggc_cleared_alloc<machine_function> (); |
| } |
| |
| /* Implement the TARGET_OPTION_OVERRIDE hook. */ |
| |
| static void |
| bfin_option_override (void) |
| { |
| /* If processor type is not specified, enable all workarounds. */ |
| if (bfin_cpu_type == BFIN_CPU_UNKNOWN) |
| { |
| int i; |
| |
| for (i = 0; bfin_cpus[i].name != NULL; i++) |
| bfin_workarounds |= bfin_cpus[i].workarounds; |
| |
| bfin_si_revision = 0xffff; |
| } |
| |
| if (bfin_csync_anomaly == 1) |
| bfin_workarounds |= WA_SPECULATIVE_SYNCS; |
| else if (bfin_csync_anomaly == 0) |
| bfin_workarounds &= ~WA_SPECULATIVE_SYNCS; |
| |
| if (bfin_specld_anomaly == 1) |
| bfin_workarounds |= WA_SPECULATIVE_LOADS; |
| else if (bfin_specld_anomaly == 0) |
| bfin_workarounds &= ~WA_SPECULATIVE_LOADS; |
| |
| if (TARGET_OMIT_LEAF_FRAME_POINTER) |
| flag_omit_frame_pointer = 1; |
| |
| #ifdef SUBTARGET_FDPIC_NOT_SUPPORTED |
| if (TARGET_FDPIC) |
| error ("-mfdpic is not supported, please use a bfin-linux-uclibc target"); |
| #endif |
| |
| /* Library identification */ |
| if (global_options_set.x_bfin_library_id && ! TARGET_ID_SHARED_LIBRARY) |
| error ("-mshared-library-id= specified without -mid-shared-library"); |
| |
| if (stack_limit_rtx && TARGET_FDPIC) |
| { |
| warning (0, "-fstack-limit- options are ignored with -mfdpic; use -mstack-check-l1"); |
| stack_limit_rtx = NULL_RTX; |
| } |
| |
| if (stack_limit_rtx && TARGET_STACK_CHECK_L1) |
| error ("can%'t use multiple stack checking methods together"); |
| |
| if (TARGET_ID_SHARED_LIBRARY && TARGET_FDPIC) |
| error ("ID shared libraries and FD-PIC mode can%'t be used together"); |
| |
| /* Don't allow the user to specify -mid-shared-library and -msep-data |
| together, as it makes little sense from a user's point of view... */ |
| if (TARGET_SEP_DATA && TARGET_ID_SHARED_LIBRARY) |
| error ("cannot specify both -msep-data and -mid-shared-library"); |
| /* ... internally, however, it's nearly the same. */ |
| if (TARGET_SEP_DATA) |
| target_flags |= MASK_ID_SHARED_LIBRARY | MASK_LEAF_ID_SHARED_LIBRARY; |
| |
| if (TARGET_ID_SHARED_LIBRARY && flag_pic == 0) |
| flag_pic = 1; |
| |
| /* There is no single unaligned SI op for PIC code. Sometimes we |
| need to use ".4byte" and sometimes we need to use ".picptr". |
| See bfin_assemble_integer for details. */ |
| if (TARGET_FDPIC) |
| targetm.asm_out.unaligned_op.si = 0; |
| |
| /* Silently turn off flag_pic if not doing FDPIC or ID shared libraries, |
| since we don't support it and it'll just break. */ |
| if (flag_pic && !TARGET_FDPIC && !TARGET_ID_SHARED_LIBRARY) |
| flag_pic = 0; |
| |
| if (TARGET_MULTICORE && bfin_cpu_type != BFIN_CPU_BF561) |
| error ("-mmulticore can only be used with BF561"); |
| |
| if (TARGET_COREA && !TARGET_MULTICORE) |
| error ("-mcorea should be used with -mmulticore"); |
| |
| if (TARGET_COREB && !TARGET_MULTICORE) |
| error ("-mcoreb should be used with -mmulticore"); |
| |
| if (TARGET_COREA && TARGET_COREB) |
| error ("-mcorea and -mcoreb can%'t be used together"); |
| |
| flag_schedule_insns = 0; |
| |
| init_machine_status = bfin_init_machine_status; |
| } |
| |
| /* Return the destination address of BRANCH. |
| We need to use this instead of get_attr_length, because the |
| cbranch_with_nops pattern conservatively sets its length to 6, and |
| we still prefer to use shorter sequences. */ |
| |
| static int |
| branch_dest (rtx_insn *branch) |
| { |
| rtx dest; |
| int dest_uid; |
| rtx pat = PATTERN (branch); |
| if (GET_CODE (pat) == PARALLEL) |
| pat = XVECEXP (pat, 0, 0); |
| dest = SET_SRC (pat); |
| if (GET_CODE (dest) == IF_THEN_ELSE) |
| dest = XEXP (dest, 1); |
| dest = XEXP (dest, 0); |
| dest_uid = INSN_UID (dest); |
| return INSN_ADDRESSES (dest_uid); |
| } |
| |
| /* Return nonzero if INSN is annotated with a REG_BR_PROB note that indicates |
| it's a branch that's predicted taken. */ |
| |
| static int |
| cbranch_predicted_taken_p (rtx insn) |
| { |
| rtx x = find_reg_note (insn, REG_BR_PROB, 0); |
| |
| if (x) |
| { |
| int pred_val = XINT (x, 0); |
| |
| return pred_val >= REG_BR_PROB_BASE / 2; |
| } |
| |
| return 0; |
| } |
| |
| /* Templates for use by asm_conditional_branch. */ |
| |
| static const char *ccbranch_templates[][3] = { |
| { "if !cc jump %3;", "if cc jump 4 (bp); jump.s %3;", "if cc jump 6 (bp); jump.l %3;" }, |
| { "if cc jump %3;", "if !cc jump 4 (bp); jump.s %3;", "if !cc jump 6 (bp); jump.l %3;" }, |
| { "if !cc jump %3 (bp);", "if cc jump 4; jump.s %3;", "if cc jump 6; jump.l %3;" }, |
| { "if cc jump %3 (bp);", "if !cc jump 4; jump.s %3;", "if !cc jump 6; jump.l %3;" }, |
| }; |
| |
| /* Output INSN, which is a conditional branch instruction with operands |
| OPERANDS. |
| |
| We deal with the various forms of conditional branches that can be generated |
| by bfin_reorg to prevent the hardware from doing speculative loads, by |
| - emitting a sufficient number of nops, if N_NOPS is nonzero, or |
| - always emitting the branch as predicted taken, if PREDICT_TAKEN is true. |
| Either of these is only necessary if the branch is short, otherwise the |
| template we use ends in an unconditional jump which flushes the pipeline |
| anyway. */ |
| |
| void |
| asm_conditional_branch (rtx_insn *insn, rtx *operands, int n_nops, int predict_taken) |
| { |
| int offset = branch_dest (insn) - INSN_ADDRESSES (INSN_UID (insn)); |
| /* Note : offset for instructions like if cc jmp; jump.[sl] offset |
| is to be taken from start of if cc rather than jump. |
| Range for jump.s is (-4094, 4096) instead of (-4096, 4094) |
| */ |
| int len = (offset >= -1024 && offset <= 1022 ? 0 |
| : offset >= -4094 && offset <= 4096 ? 1 |
| : 2); |
| int bp = predict_taken && len == 0 ? 1 : cbranch_predicted_taken_p (insn); |
| int idx = (bp << 1) | (GET_CODE (operands[0]) == EQ ? BRF : BRT); |
| output_asm_insn (ccbranch_templates[idx][len], operands); |
| gcc_assert (n_nops == 0 || !bp); |
| if (len == 0) |
| while (n_nops-- > 0) |
| output_asm_insn ("nop;", NULL); |
| } |
| |
| /* Emit rtl for a comparison operation CMP in mode MODE. Operands have been |
| stored in bfin_compare_op0 and bfin_compare_op1 already. */ |
| |
| rtx |
| bfin_gen_compare (rtx cmp, machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| enum rtx_code code1, code2; |
| rtx op0 = XEXP (cmp, 0), op1 = XEXP (cmp, 1); |
| rtx tem = bfin_cc_rtx; |
| enum rtx_code code = GET_CODE (cmp); |
| |
| /* If we have a BImode input, then we already have a compare result, and |
| do not need to emit another comparison. */ |
| if (GET_MODE (op0) == BImode) |
| { |
| gcc_assert ((code == NE || code == EQ) && op1 == const0_rtx); |
| tem = op0, code2 = code; |
| } |
| else |
| { |
| switch (code) { |
| /* bfin has these conditions */ |
| case EQ: |
| case LT: |
| case LE: |
| case LEU: |
| case LTU: |
| code1 = code; |
| code2 = NE; |
| break; |
| default: |
| code1 = reverse_condition (code); |
| code2 = EQ; |
| break; |
| } |
| emit_insn (gen_rtx_SET (VOIDmode, tem, |
| gen_rtx_fmt_ee (code1, BImode, op0, op1))); |
| } |
| |
| return gen_rtx_fmt_ee (code2, BImode, tem, CONST0_RTX (BImode)); |
| } |
| |
| /* Return nonzero iff C has exactly one bit set if it is interpreted |
| as a 32-bit constant. */ |
| |
| int |
| log2constp (unsigned HOST_WIDE_INT c) |
| { |
| c &= 0xFFFFFFFF; |
| return c != 0 && (c & (c-1)) == 0; |
| } |
| |
| /* Returns the number of consecutive least significant zeros in the binary |
| representation of *V. |
| We modify *V to contain the original value arithmetically shifted right by |
| the number of zeroes. */ |
| |
| static int |
| shiftr_zero (HOST_WIDE_INT *v) |
| { |
| unsigned HOST_WIDE_INT tmp = *v; |
| unsigned HOST_WIDE_INT sgn; |
| int n = 0; |
| |
| if (tmp == 0) |
| return 0; |
| |
| sgn = tmp & ((unsigned HOST_WIDE_INT) 1 << (HOST_BITS_PER_WIDE_INT - 1)); |
| while ((tmp & 0x1) == 0 && n <= 32) |
| { |
| tmp = (tmp >> 1) | sgn; |
| n++; |
| } |
| *v = tmp; |
| return n; |
| } |
| |
| /* After reload, split the load of an immediate constant. OPERANDS are the |
| operands of the movsi_insn pattern which we are splitting. We return |
| nonzero if we emitted a sequence to load the constant, zero if we emitted |
| nothing because we want to use the splitter's default sequence. */ |
| |
| int |
| split_load_immediate (rtx operands[]) |
| { |
| HOST_WIDE_INT val = INTVAL (operands[1]); |
| HOST_WIDE_INT tmp; |
| HOST_WIDE_INT shifted = val; |
| HOST_WIDE_INT shifted_compl = ~val; |
| int num_zero = shiftr_zero (&shifted); |
| int num_compl_zero = shiftr_zero (&shifted_compl); |
| unsigned int regno = REGNO (operands[0]); |
| |
| /* This case takes care of single-bit set/clear constants, which we could |
| also implement with BITSET/BITCLR. */ |
| if (num_zero |
| && shifted >= -32768 && shifted < 65536 |
| && (D_REGNO_P (regno) |
| || (regno >= REG_P0 && regno <= REG_P7 && num_zero <= 2))) |
| { |
| emit_insn (gen_movsi (operands[0], gen_int_mode (shifted, SImode))); |
| emit_insn (gen_ashlsi3 (operands[0], operands[0], GEN_INT (num_zero))); |
| return 1; |
| } |
| |
| tmp = val & 0xFFFF; |
| tmp |= -(tmp & 0x8000); |
| |
| /* If high word has one bit set or clear, try to use a bit operation. */ |
| if (D_REGNO_P (regno)) |
| { |
| if (log2constp (val & 0xFFFF0000)) |
| { |
| emit_insn (gen_movsi (operands[0], GEN_INT (val & 0xFFFF))); |
| emit_insn (gen_iorsi3 (operands[0], operands[0], |
| gen_int_mode (val & 0xFFFF0000, SImode))); |
| return 1; |
| } |
| else if (log2constp (val | 0xFFFF) && (val & 0x8000) != 0) |
| { |
| emit_insn (gen_movsi (operands[0], GEN_INT (tmp))); |
| emit_insn (gen_andsi3 (operands[0], operands[0], |
| gen_int_mode (val | 0xFFFF, SImode))); |
| } |
| } |
| |
| if (D_REGNO_P (regno)) |
| { |
| if (tmp >= -64 && tmp <= 63) |
| { |
| emit_insn (gen_movsi (operands[0], GEN_INT (tmp))); |
| emit_insn (gen_movstricthi_high (operands[0], |
| gen_int_mode (val & -65536, |
| SImode))); |
| return 1; |
| } |
| |
| if ((val & 0xFFFF0000) == 0) |
| { |
| emit_insn (gen_movsi (operands[0], const0_rtx)); |
| emit_insn (gen_movsi_low (operands[0], operands[0], operands[1])); |
| return 1; |
| } |
| |
| if ((val & 0xFFFF0000) == 0xFFFF0000) |
| { |
| emit_insn (gen_movsi (operands[0], constm1_rtx)); |
| emit_insn (gen_movsi_low (operands[0], operands[0], operands[1])); |
| return 1; |
| } |
| } |
| |
| /* Need DREGs for the remaining case. */ |
| if (regno > REG_R7) |
| return 0; |
| |
| if (optimize_size |
| && num_compl_zero && shifted_compl >= -64 && shifted_compl <= 63) |
| { |
| /* If optimizing for size, generate a sequence that has more instructions |
| but is shorter. */ |
| emit_insn (gen_movsi (operands[0], gen_int_mode (shifted_compl, SImode))); |
| emit_insn (gen_ashlsi3 (operands[0], operands[0], |
| GEN_INT (num_compl_zero))); |
| emit_insn (gen_one_cmplsi2 (operands[0], operands[0])); |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* Return true if the legitimate memory address for a memory operand of mode |
| MODE. Return false if not. */ |
| |
| static bool |
| bfin_valid_add (machine_mode mode, HOST_WIDE_INT value) |
| { |
| unsigned HOST_WIDE_INT v = value > 0 ? value : -value; |
| int sz = GET_MODE_SIZE (mode); |
| int shift = sz == 1 ? 0 : sz == 2 ? 1 : 2; |
| /* The usual offsettable_memref machinery doesn't work so well for this |
| port, so we deal with the problem here. */ |
| if (value > 0 && sz == 8) |
| v += 4; |
| return (v & ~(0x7fff << shift)) == 0; |
| } |
| |
| static bool |
| bfin_valid_reg_p (unsigned int regno, int strict, machine_mode mode, |
| enum rtx_code outer_code) |
| { |
| if (strict) |
| return REGNO_OK_FOR_BASE_STRICT_P (regno, mode, outer_code, SCRATCH); |
| else |
| return REGNO_OK_FOR_BASE_NONSTRICT_P (regno, mode, outer_code, SCRATCH); |
| } |
| |
| /* Recognize an RTL expression that is a valid memory address for an |
| instruction. The MODE argument is the machine mode for the MEM expression |
| that wants to use this address. |
| |
| Blackfin addressing modes are as follows: |
| |
| [preg] |
| [preg + imm16] |
| |
| B [ Preg + uimm15 ] |
| W [ Preg + uimm16m2 ] |
| [ Preg + uimm17m4 ] |
| |
| [preg++] |
| [preg--] |
| [--sp] |
| */ |
| |
| static bool |
| bfin_legitimate_address_p (machine_mode mode, rtx x, bool strict) |
| { |
| switch (GET_CODE (x)) { |
| case REG: |
| if (bfin_valid_reg_p (REGNO (x), strict, mode, MEM)) |
| return true; |
| break; |
| case PLUS: |
| if (REG_P (XEXP (x, 0)) |
| && bfin_valid_reg_p (REGNO (XEXP (x, 0)), strict, mode, PLUS) |
| && ((GET_CODE (XEXP (x, 1)) == UNSPEC && mode == SImode) |
| || (GET_CODE (XEXP (x, 1)) == CONST_INT |
| && bfin_valid_add (mode, INTVAL (XEXP (x, 1)))))) |
| return true; |
| break; |
| case POST_INC: |
| case POST_DEC: |
| if (LEGITIMATE_MODE_FOR_AUTOINC_P (mode) |
| && REG_P (XEXP (x, 0)) |
| && bfin_valid_reg_p (REGNO (XEXP (x, 0)), strict, mode, POST_INC)) |
| return true; |
| case PRE_DEC: |
| if (LEGITIMATE_MODE_FOR_AUTOINC_P (mode) |
| && XEXP (x, 0) == stack_pointer_rtx |
| && REG_P (XEXP (x, 0)) |
| && bfin_valid_reg_p (REGNO (XEXP (x, 0)), strict, mode, PRE_DEC)) |
| return true; |
| break; |
| default: |
| break; |
| } |
| return false; |
| } |
| |
| /* Decide whether we can force certain constants to memory. If we |
| decide we can't, the caller should be able to cope with it in |
| another way. */ |
| |
| static bool |
| bfin_cannot_force_const_mem (machine_mode mode ATTRIBUTE_UNUSED, |
| rtx x ATTRIBUTE_UNUSED) |
| { |
| /* We have only one class of non-legitimate constants, and our movsi |
| expander knows how to handle them. Dropping these constants into the |
| data section would only shift the problem - we'd still get relocs |
| outside the object, in the data section rather than the text section. */ |
| return true; |
| } |
| |
| /* Ensure that for any constant of the form symbol + offset, the offset |
| remains within the object. Any other constants are ok. |
| This ensures that flat binaries never have to deal with relocations |
| crossing section boundaries. */ |
| |
| static bool |
| bfin_legitimate_constant_p (machine_mode mode ATTRIBUTE_UNUSED, rtx x) |
| { |
| rtx sym; |
| HOST_WIDE_INT offset; |
| |
| if (GET_CODE (x) != CONST) |
| return true; |
| |
| x = XEXP (x, 0); |
| gcc_assert (GET_CODE (x) == PLUS); |
| |
| sym = XEXP (x, 0); |
| x = XEXP (x, 1); |
| if (GET_CODE (sym) != SYMBOL_REF |
| || GET_CODE (x) != CONST_INT) |
| return true; |
| offset = INTVAL (x); |
| |
| if (SYMBOL_REF_DECL (sym) == 0) |
| return true; |
| if (offset < 0 |
| || offset >= int_size_in_bytes (TREE_TYPE (SYMBOL_REF_DECL (sym)))) |
| return false; |
| |
| return true; |
| } |
| |
| static bool |
| bfin_rtx_costs (rtx x, int code_i, int outer_code_i, int opno, int *total, |
| bool speed) |
| { |
| enum rtx_code code = (enum rtx_code) code_i; |
| enum rtx_code outer_code = (enum rtx_code) outer_code_i; |
| int cost2 = COSTS_N_INSNS (1); |
| rtx op0, op1; |
| |
| switch (code) |
| { |
| case CONST_INT: |
| if (outer_code == SET || outer_code == PLUS) |
| *total = satisfies_constraint_Ks7 (x) ? 0 : cost2; |
| else if (outer_code == AND) |
| *total = log2constp (~INTVAL (x)) ? 0 : cost2; |
| else if (outer_code == LE || outer_code == LT || outer_code == EQ) |
| *total = (INTVAL (x) >= -4 && INTVAL (x) <= 3) ? 0 : cost2; |
| else if (outer_code == LEU || outer_code == LTU) |
| *total = (INTVAL (x) >= 0 && INTVAL (x) <= 7) ? 0 : cost2; |
| else if (outer_code == MULT) |
| *total = (INTVAL (x) == 2 || INTVAL (x) == 4) ? 0 : cost2; |
| else if (outer_code == ASHIFT && (INTVAL (x) == 1 || INTVAL (x) == 2)) |
| *total = 0; |
| else if (outer_code == ASHIFT || outer_code == ASHIFTRT |
| || outer_code == LSHIFTRT) |
| *total = (INTVAL (x) >= 0 && INTVAL (x) <= 31) ? 0 : cost2; |
| else if (outer_code == IOR || outer_code == XOR) |
| *total = (INTVAL (x) & (INTVAL (x) - 1)) == 0 ? 0 : cost2; |
| else |
| *total = cost2; |
| return true; |
| |
| case CONST: |
| case LABEL_REF: |
| case SYMBOL_REF: |
| case CONST_DOUBLE: |
| *total = COSTS_N_INSNS (2); |
| return true; |
| |
| case PLUS: |
| op0 = XEXP (x, 0); |
| op1 = XEXP (x, 1); |
| if (GET_MODE (x) == SImode) |
| { |
| if (GET_CODE (op0) == MULT |
| && GET_CODE (XEXP (op0, 1)) == CONST_INT) |
| { |
| HOST_WIDE_INT val = INTVAL (XEXP (op0, 1)); |
| if (val == 2 || val == 4) |
| { |
| *total = cost2; |
| *total += rtx_cost (XEXP (op0, 0), outer_code, opno, speed); |
| *total += rtx_cost (op1, outer_code, opno, speed); |
| return true; |
| } |
| } |
| *total = cost2; |
| if (GET_CODE (op0) != REG |
| && (GET_CODE (op0) != SUBREG || GET_CODE (SUBREG_REG (op0)) != REG)) |
| *total += set_src_cost (op0, speed); |
| #if 0 /* We'd like to do this for accuracy, but it biases the loop optimizer |
| towards creating too many induction variables. */ |
| if (!reg_or_7bit_operand (op1, SImode)) |
| *total += set_src_cost (op1, speed); |
| #endif |
| } |
| else if (GET_MODE (x) == DImode) |
| { |
| *total = 6 * cost2; |
| if (GET_CODE (op1) != CONST_INT |
| || !satisfies_constraint_Ks7 (op1)) |
| *total += rtx_cost (op1, PLUS, 1, speed); |
| if (GET_CODE (op0) != REG |
| && (GET_CODE (op0) != SUBREG || GET_CODE (SUBREG_REG (op0)) != REG)) |
| *total += rtx_cost (op0, PLUS, 0, speed); |
| } |
| return true; |
| |
| case MINUS: |
| if (GET_MODE (x) == DImode) |
| *total = 6 * cost2; |
| else |
| *total = cost2; |
| return true; |
| |
| case ASHIFT: |
| case ASHIFTRT: |
| case LSHIFTRT: |
| if (GET_MODE (x) == DImode) |
| *total = 6 * cost2; |
| else |
| *total = cost2; |
| |
| op0 = XEXP (x, 0); |
| op1 = XEXP (x, 1); |
| if (GET_CODE (op0) != REG |
| && (GET_CODE (op0) != SUBREG || GET_CODE (SUBREG_REG (op0)) != REG)) |
| *total += rtx_cost (op0, code, 0, speed); |
| |
| return true; |
| |
| case IOR: |
| case AND: |
| case XOR: |
| op0 = XEXP (x, 0); |
| op1 = XEXP (x, 1); |
| |
| /* Handle special cases of IOR: rotates, ALIGN insns, movstricthi_high. */ |
| if (code == IOR) |
| { |
| if ((GET_CODE (op0) == LSHIFTRT && GET_CODE (op1) == ASHIFT) |
| || (GET_CODE (op0) == ASHIFT && GET_CODE (op1) == ZERO_EXTEND) |
| || (GET_CODE (op0) == ASHIFT && GET_CODE (op1) == LSHIFTRT) |
| || (GET_CODE (op0) == AND && GET_CODE (op1) == CONST_INT)) |
| { |
| *total = cost2; |
| return true; |
| } |
| } |
| |
| if (GET_CODE (op0) != REG |
| && (GET_CODE (op0) != SUBREG || GET_CODE (SUBREG_REG (op0)) != REG)) |
| *total += rtx_cost (op0, code, 0, speed); |
| |
| if (GET_MODE (x) == DImode) |
| { |
| *total = 2 * cost2; |
| return true; |
| } |
| *total = cost2; |
| if (GET_MODE (x) != SImode) |
| return true; |
| |
| if (code == AND) |
| { |
| if (! rhs_andsi3_operand (XEXP (x, 1), SImode)) |
| *total += rtx_cost (XEXP (x, 1), code, 1, speed); |
| } |
| else |
| { |
| if (! regorlog2_operand (XEXP (x, 1), SImode)) |
| *total += rtx_cost (XEXP (x, 1), code, 1, speed); |
| } |
| |
| return true; |
| |
| case ZERO_EXTRACT: |
| case SIGN_EXTRACT: |
| if (outer_code == SET |
| && XEXP (x, 1) == const1_rtx |
| && GET_CODE (XEXP (x, 2)) == CONST_INT) |
| { |
| *total = 2 * cost2; |
| return true; |
| } |
| /* fall through */ |
| |
| case SIGN_EXTEND: |
| case ZERO_EXTEND: |
| *total = cost2; |
| return true; |
| |
| case MULT: |
| { |
| op0 = XEXP (x, 0); |
| op1 = XEXP (x, 1); |
| if (GET_CODE (op0) == GET_CODE (op1) |
| && (GET_CODE (op0) == ZERO_EXTEND |
| || GET_CODE (op0) == SIGN_EXTEND)) |
| { |
| *total = COSTS_N_INSNS (1); |
| op0 = XEXP (op0, 0); |
| op1 = XEXP (op1, 0); |
| } |
| else if (!speed) |
| *total = COSTS_N_INSNS (1); |
| else |
| *total = COSTS_N_INSNS (3); |
| |
| if (GET_CODE (op0) != REG |
| && (GET_CODE (op0) != SUBREG || GET_CODE (SUBREG_REG (op0)) != REG)) |
| *total += rtx_cost (op0, MULT, 0, speed); |
| if (GET_CODE (op1) != REG |
| && (GET_CODE (op1) != SUBREG || GET_CODE (SUBREG_REG (op1)) != REG)) |
| *total += rtx_cost (op1, MULT, 1, speed); |
| } |
| return true; |
| |
| case UDIV: |
| case UMOD: |
| *total = COSTS_N_INSNS (32); |
| return true; |
| |
| case VEC_CONCAT: |
| case VEC_SELECT: |
| if (outer_code == SET) |
| *total = cost2; |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| /* Used for communication between {push,pop}_multiple_operation (which |
| we use not only as a predicate) and the corresponding output functions. */ |
| static int first_preg_to_save, first_dreg_to_save; |
| static int n_regs_to_save; |
| |
| int |
| analyze_push_multiple_operation (rtx op) |
| { |
| int lastdreg = 8, lastpreg = 6; |
| int i, group; |
| |
| first_preg_to_save = lastpreg; |
| first_dreg_to_save = lastdreg; |
| for (i = 1, group = 0; i < XVECLEN (op, 0) - 1; i++) |
| { |
| rtx t = XVECEXP (op, 0, i); |
| rtx src, dest; |
| int regno; |
| |
| if (GET_CODE (t) != SET) |
| return 0; |
| |
| src = SET_SRC (t); |
| dest = SET_DEST (t); |
| if (GET_CODE (dest) != MEM || ! REG_P (src)) |
| return 0; |
| dest = XEXP (dest, 0); |
| if (GET_CODE (dest) != PLUS |
| || ! REG_P (XEXP (dest, 0)) |
| || REGNO (XEXP (dest, 0)) != REG_SP |
| || GET_CODE (XEXP (dest, 1)) != CONST_INT |
| || INTVAL (XEXP (dest, 1)) != -i * 4) |
| return 0; |
| |
| regno = REGNO (src); |
| if (group == 0) |
| { |
| if (D_REGNO_P (regno)) |
| { |
| group = 1; |
| first_dreg_to_save = lastdreg = regno - REG_R0; |
| } |
| else if (regno >= REG_P0 && regno <= REG_P7) |
|