/* The Blackfin code generation auxiliary output file. Copyright (C) 2005 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 2, 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 COPYING. If not, write to the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "config.h" #include "system.h" #include "coretypes.h" #include "tm.h" #include "rtl.h" #include "regs.h" #include "hard-reg-set.h" #include "real.h" #include "insn-config.h" #include "insn-codes.h" #include "conditions.h" #include "insn-flags.h" #include "output.h" #include "insn-attr.h" #include "tree.h" #include "flags.h" #include "except.h" #include "function.h" #include "input.h" #include "target.h" #include "target-def.h" #include "expr.h" #include "toplev.h" #include "recog.h" #include "ggc.h" #include "integrate.h" #include "langhooks.h" #include "bfin-protos.h" #include "tm-preds.h" #include "gt-bfin.h" /* Test and compare insns in bfin.md store the information needed to generate branch and scc insns here. */ rtx bfin_compare_op0, bfin_compare_op1; /* 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; /* Nonzero if -mshared-library-id was given. */ static int bfin_lib_id_given; 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", input_filename); for (i = 0; arg_regs[i] >= 0; i++) ; max_arg_registers = i; /* how many arg reg used */ } /* Called early in the compilation to conditionally modify fixed_regs/call_used_regs. */ void conditional_register_usage (void) { /* initialize condition code flag register rtx */ bfin_cc_rtx = gen_rtx_REG (BImode, REG_CC); bfin_rets_rtx = gen_rtx_REG (Pmode, REG_RETS); } /* Examine machine-dependent attributes of function type FUNTYPE and return its type. See the definition of E_FUNKIND. */ static e_funkind funkind (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. */ rtx legitimize_pic_address (rtx orig, rtx reg, rtx picreg) { rtx addr = orig; rtx new = orig; if (GET_CODE (addr) == SYMBOL_REF || GET_CODE (addr) == LABEL_REF) { if (GET_CODE (addr) == SYMBOL_REF && CONSTANT_POOL_ADDRESS_P (addr)) reg = new = orig; else { if (reg == 0) { gcc_assert (!no_new_pseudos); reg = gen_reg_rtx (Pmode); } if (flag_pic == 2) { emit_insn (gen_movsi_high_pic (reg, addr)); emit_insn (gen_movsi_low_pic (reg, reg, addr)); emit_insn (gen_addsi3 (reg, reg, picreg)); new = gen_const_mem (Pmode, reg); } else { rtx tmp = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, addr), UNSPEC_MOVE_PIC); new = gen_const_mem (Pmode, gen_rtx_PLUS (Pmode, picreg, tmp)); } emit_move_insn (reg, new); } if (picreg == pic_offset_table_rtx) current_function_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 (!no_new_pseudos); 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; } /* Stack frame layout. */ /* 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. */ static int n_dregs_to_save (bool is_inthandler) { unsigned i; for (i = REG_R0; i <= REG_R7; i++) { if (regs_ever_live[i] && (is_inthandler || ! call_used_regs[i])) return REG_R7 - i + 1; if (current_function_calls_eh_return) { unsigned j; for (j = 0; ; j++) { unsigned test = EH_RETURN_DATA_REGNO (j); if (test == INVALID_REGNUM) break; if (test == i) return REG_R7 - i + 1; } } } return 0; } /* Like n_dregs_to_save, but compute number of PREGS to save. */ static int n_pregs_to_save (bool is_inthandler) { unsigned i; for (i = REG_P0; i <= REG_P5; i++) if ((regs_ever_live[i] && (is_inthandler || ! call_used_regs[i])) || (i == PIC_OFFSET_TABLE_REGNUM && (current_function_uses_pic_offset_table || (TARGET_ID_SHARED_LIBRARY && ! current_function_is_leaf)))) return REG_P5 - i + 1; return 0; } /* Determine if we are going to save the frame pointer in the prologue. */ static bool must_save_fp_p (void) { return frame_pointer_needed || regs_ever_live[REG_FP]; } 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 (current_function_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) { int ndregs = saveall ? 8 : n_dregs_to_save (is_inthandler); int npregs = saveall ? 6 : n_pregs_to_save (is_inthandler); int dregno = REG_R7 + 1 - ndregs; int pregno = REG_P5 + 1 - npregs; int total = ndregs + npregs; int i; rtx pat, insn, val; if (total == 0) return; val = GEN_INT (-total * 4); pat = gen_rtx_PARALLEL (VOIDmode, rtvec_alloc (total + 2)); XVECEXP (pat, 0, 0) = gen_rtx_UNSPEC (VOIDmode, gen_rtvec (1, val), UNSPEC_PUSH_MULTIPLE); XVECEXP (pat, 0, total + 1) = gen_rtx_SET (VOIDmode, spreg, gen_rtx_PLUS (Pmode, spreg, val)); RTX_FRAME_RELATED_P (XVECEXP (pat, 0, total + 1)) = 1; for (i = 0; i < total; i++) { rtx memref = gen_rtx_MEM (word_mode, gen_rtx_PLUS (Pmode, spreg, GEN_INT (- i * 4 - 4))); rtx subpat; if (ndregs > 0) { subpat = gen_rtx_SET (VOIDmode, memref, gen_rtx_REG (word_mode, dregno++)); ndregs--; } else { subpat = gen_rtx_SET (VOIDmode, memref, gen_rtx_REG (word_mode, pregno++)); npregs++; } XVECEXP (pat, 0, i + 1) = subpat; RTX_FRAME_RELATED_P (subpat) = 1; } insn = emit_insn (pat); 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) { int ndregs = saveall ? 8 : n_dregs_to_save (is_inthandler); int npregs = saveall ? 6 : n_pregs_to_save (is_inthandler); int total = ndregs + npregs; int i, regno; rtx pat, insn; if (total == 0) return; pat = gen_rtx_PARALLEL (VOIDmode, rtvec_alloc (total + 1)); XVECEXP (pat, 0, 0) = gen_rtx_SET (VOIDmode, spreg, gen_rtx_PLUS (Pmode, spreg, GEN_INT (total * 4))); if (npregs > 0) regno = REG_P5 + 1; else regno = REG_R7 + 1; for (i = 0; i < total; 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 > 0) { if (--npregs == 0) regno = REG_R7 + 1; } } insn = emit_insn (pat); RTX_FRAME_RELATED_P (insn) = 1; } /* 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 *cum, enum 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 = cum->words + 1; i < max_arg_registers; i++) { mem = gen_rtx_MEM (Pmode, plus_constant (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. */ int bfin_frame_pointer_required (void) { e_funkind fkind = funkind (TREE_TYPE (current_function_decl)); if (fkind != SUBROUTINE) return 1; /* 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 && ! current_function_is_leaf) return 1; return 0; } /* 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 && !current_function_is_leaf)); int ndregs = all ? 8 : n_dregs_to_save (is_inthandler); int npregs = all ? 6 : n_pregs_to_save (is_inthandler); int n = ndregs + npregs; if (all || stack_frame_needed_p ()) /* We use a LINK instruction in this case. */ n += 2; else { if (must_save_fp_p ()) n++; if (! current_function_is_leaf) n++; } if (fkind != SUBROUTINE) { int i; /* Increment once for ASTAT. */ n++; /* RETE/X/N. */ if (lookup_attribute ("nesting", attrs)) n++; for (i = REG_P7 + 1; i < REG_CC; i++) if (all || regs_ever_live[i] || (!leaf_function_p () && call_used_regs[i])) n += i == REG_A0 || i == REG_A1 ? 2 : 1; } return n; } /* 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 (current_function_outgoing_args_size >= FIXED_STACK_AREA) offset += current_function_outgoing_args_size; else if (current_function_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; 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 the frame pointer. We can use P1 as a scratch register. Set RTX_FRAME_RELATED_P on the generated insns if FRAME is nonzero. */ static void add_to_sp (rtx spreg, HOST_WIDE_INT value, int frame) { 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 = gen_rtx_REG (SImode, REG_P1); rtx insn; if (frame) frame_related_constant_load (tmpreg, value, TRUE); else { insn = emit_move_insn (tmpreg, GEN_INT (value)); if (frame) RTX_FRAME_RELATED_P (insn) = 1; } insn = emit_insn (gen_addsi3 (spreg, spreg, tmpreg)); if (frame) RTX_FRAME_RELATED_P (insn) = 1; } else do { int size = value; rtx 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 (spreg, spreg, 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; 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 (current_function_outgoing_args_size) { if (current_function_outgoing_args_size >= FIXED_STACK_AREA) return current_function_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_fp_p () && ! current_function_is_leaf)) emit_link_insn (spreg, frame_size); else { if (! current_function_is_leaf) { rtx pat = gen_movsi (gen_rtx_MEM (Pmode, gen_rtx_PRE_DEC (Pmode, spreg)), bfin_rets_rtx); rtx 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 = emit_insn (pat); RTX_FRAME_RELATED_P (insn) = 1; } add_to_sp (spreg, -frame_size, 1); } } /* Like do_link, but used for epilogues to deallocate the stack frame. */ static void do_unlink (rtx spreg, HOST_WIDE_INT frame_size, bool all) { frame_size += arg_area_size (); if (all || stack_frame_needed_p ()) emit_insn (gen_unlink ()); else { rtx postinc = gen_rtx_MEM (Pmode, gen_rtx_POST_INC (Pmode, spreg)); add_to_sp (spreg, frame_size, 0); if (must_save_fp_p ()) { rtx fpreg = gen_rtx_REG (Pmode, REG_FP); emit_move_insn (fpreg, postinc); emit_insn (gen_rtx_USE (VOIDmode, fpreg)); } if (! current_function_is_leaf) { emit_move_insn (bfin_rets_rtx, postinc); emit_insn (gen_rtx_USE (VOIDmode, 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) { int i; 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; tree attrs = TYPE_ATTRIBUTES (TREE_TYPE (current_function_decl)); bool all = lookup_attribute ("saveall", attrs) != NULL_TREE; 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; } insn = emit_move_insn (predec, gen_rtx_REG (SImode, REG_ASTAT)); 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 (!current_function_is_leaf) all = true; expand_prologue_reg_save (spreg, all, true); for (i = REG_P7 + 1; i < REG_CC; i++) if (all || regs_ever_live[i] || (!leaf_function_p () && call_used_regs[i])) { 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; } if (lookup_attribute ("nesting", attrs)) { rtx srcreg = gen_rtx_REG (Pmode, (fkind == EXCPT_HANDLER ? REG_RETX : fkind == NMI_HANDLER ? REG_RETN : REG_RETI)); 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); rtx insn; insn = emit_move_insn (r0reg, gen_rtx_REG (SImode, REG_SEQSTAT)); REG_NOTES (insn) = gen_rtx_EXPR_LIST (REG_MAYBE_DEAD, const0_rtx, NULL_RTX); insn = emit_insn (gen_ashrsi3 (r0reg, r0reg, GEN_INT (26))); REG_NOTES (insn) = gen_rtx_EXPR_LIST (REG_MAYBE_DEAD, const0_rtx, NULL_RTX); insn = emit_insn (gen_ashlsi3 (r0reg, r0reg, GEN_INT (26))); REG_NOTES (insn) = gen_rtx_EXPR_LIST (REG_MAYBE_DEAD, const0_rtx, NULL_RTX); insn = emit_move_insn (r1reg, spreg); REG_NOTES (insn) = gen_rtx_EXPR_LIST (REG_MAYBE_DEAD, const0_rtx, NULL_RTX); insn = emit_move_insn (r2reg, gen_rtx_REG (Pmode, REG_FP)); REG_NOTES (insn) = gen_rtx_EXPR_LIST (REG_MAYBE_DEAD, const0_rtx, NULL_RTX); insn = emit_insn (gen_addsi3 (r2reg, r2reg, GEN_INT (8))); REG_NOTES (insn) = gen_rtx_EXPR_LIST (REG_MAYBE_DEAD, const0_rtx, NULL_RTX); } } /* 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) { int i; rtx postinc1 = gen_rtx_POST_INC (SImode, spreg); rtx postinc = gen_rtx_MEM (SImode, postinc1); tree attrs = TYPE_ATTRIBUTES (TREE_TYPE (current_function_decl)); bool all = lookup_attribute ("saveall", attrs) != NULL_TREE; /* 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); if (lookup_attribute ("nesting", attrs)) { rtx srcreg = gen_rtx_REG (Pmode, (fkind == EXCPT_HANDLER ? REG_RETX : fkind == NMI_HANDLER ? REG_RETN : REG_RETI)); 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 (!current_function_is_leaf) all = true; for (i = REG_CC - 1; i > REG_P7; i--) if (all || regs_ever_live[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); } expand_epilogue_reg_restore (spreg, all, true); emit_move_insn (gen_rtx_REG (SImode, REG_ASTAT), postinc); /* 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_INT (fkind))); } /* Used while emitting the prologue to generate code to load the correct value into the PIC register, which is passed in DEST. */ static void bfin_load_pic_reg (rtx dest) { rtx addr, insn; if (bfin_lib_id_given) addr = plus_constant (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)); insn = emit_insn (gen_movsi (dest, gen_rtx_MEM (Pmode, addr))); REG_NOTES (insn) = gen_rtx_EXPR_LIST (REG_MAYBE_DEAD, const0_rtx, NULL); } /* Generate RTL for the prologue of the current function. */ void bfin_expand_prologue (void) { rtx insn; 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; if (fkind != SUBROUTINE) { expand_interrupt_handler_prologue (spreg, fkind); return; } if (current_function_limit_stack) { HOST_WIDE_INT offset = bfin_initial_elimination_offset (ARG_POINTER_REGNUM, STACK_POINTER_REGNUM); rtx lim = stack_limit_rtx; if (GET_CODE (lim) == SYMBOL_REF) { rtx p2reg = gen_rtx_REG (Pmode, REG_P2); if (TARGET_ID_SHARED_LIBRARY) { rtx p1reg = gen_rtx_REG (Pmode, REG_P1); rtx r3reg = gen_rtx_REG (Pmode, REG_R3); rtx val; pic_reg_loaded = p2reg; bfin_load_pic_reg (pic_reg_loaded); val = legitimize_pic_address (stack_limit_rtx, p1reg, p2reg); 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 (stack_limit_rtx, offset); emit_move_insn (p2reg, limit); lim = p2reg; } } emit_insn (gen_compare_lt (bfin_cc_rtx, spreg, lim)); emit_insn (gen_trapifcc ()); } expand_prologue_reg_save (spreg, 0, false); do_link (spreg, frame_size, false); if (TARGET_ID_SHARED_LIBRARY && (current_function_uses_pic_offset_table || !current_function_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. */ void bfin_expand_epilogue (int need_return, int eh_return) { rtx spreg = gen_rtx_REG (Pmode, REG_SP); e_funkind fkind = funkind (TREE_TYPE (current_function_decl)); if (fkind != SUBROUTINE) { expand_interrupt_handler_epilogue (spreg, fkind); return; } do_unlink (spreg, get_frame_size (), false); expand_epilogue_reg_restore (spreg, false, 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_INT (SUBROUTINE))); } /* 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 && !regs_ever_live[new_reg]) return 0; return 1; } /* 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); } /* Try machine-dependent ways of modifying an illegitimate address X to be legitimate. If we find one, return the new, valid address, otherwise return NULL_RTX. OLDX is the address as it was before break_out_memory_refs was called. In some cases it is useful to look at this to decide what needs to be done. MODE is the mode of the memory reference. */ rtx legitimize_address (rtx x ATTRIBUTE_UNUSED, rtx oldx ATTRIBUTE_UNUSED, enum machine_mode mode ATTRIBUTE_UNUSED) { return NULL_RTX; } /* 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, enum 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; } 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; } /* 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) { 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) { enum machine_mode 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') { gcc_assert (REGNO (x) < 32); fprintf (file, "%s", short_reg_names[REGNO (x)]); /*fprintf (file, "\n%d\n ", REGNO (x));*/ break; } else if (code == 'd') { gcc_assert (REGNO (x) < 32); fprintf (file, "%s", high_reg_names[REGNO (x)]); break; } else if (code == 'w') { gcc_assert (REGNO (x) == REG_A0 || REGNO (x) == REG_A1); fprintf (file, "%s.w", reg_names[REGNO (x)]); } else if (code == 'x') { gcc_assert (REGNO (x) == REG_A0 || REGNO (x) == REG_A1); fprintf (file, "%s.x", reg_names[REGNO (x)]); } else if (code == 'D') { fprintf (file, "%s", dregs_pair_names[REGNO (x)]); } else if (code == 'H') { gcc_assert (mode == DImode || mode == DFmode); gcc_assert (REG_P (x)); fprintf (file, "%s", reg_names[REGNO (x) + 1]); } else if (code == 'T') { gcc_assert (D_REGNO_P (REGNO (x))); fprintf (file, "%s", byte_reg_names[REGNO (x)]); } 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: /* Moves to half registers with d or h modifiers always use unsigned constants. */ if (code == 'd') x = GEN_INT ((INTVAL (x) >> 16) & 0xffff); else if (code == 'h') x = GEN_INT (INTVAL (x) & 0xffff); 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); if (code == 'G' && flag_pic) fprintf (file, "@GOT"); 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_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.) */ void function_arg_advance (CUMULATIVE_ARGS *cum, enum machine_mode mode, tree type, int named ATTRIBUTE_UNUSED) { 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). */ struct rtx_def * function_arg (CUMULATIVE_ARGS *cum, enum machine_mode mode, tree type, int named ATTRIBUTE_UNUSED) { 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 a an argument is larger than the registers available, it will span the register and stack. */ static int bfin_arg_partial_bytes (CUMULATIVE_ARGS *cum, enum 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 = 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 *cum ATTRIBUTE_UNUSED, enum machine_mode mode ATTRIBUTE_UNUSED, 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 RETURN_IN_MEMORY. */ int bfin_return_in_memory (tree type) { 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) { e_funkind fkind = funkind (TREE_TYPE (current_function_decl)); return fkind == SUBROUTINE; } /* Emit RTL insns to initialize the variable parts of a trampoline at TRAMP. FNADDR is an RTX for the address of the function's pure code. CXT is an RTX for the static chain value for the function. */ void initialize_trampoline (tramp, fnaddr, cxt) rtx tramp, fnaddr, cxt; { rtx t1 = copy_to_reg (fnaddr); rtx t2 = copy_to_reg (cxt); rtx addr; addr = memory_address (Pmode, plus_constant (tramp, 2)); emit_move_insn (gen_rtx_MEM (HImode, addr), gen_lowpart (HImode, t1)); emit_insn (gen_ashrsi3 (t1, t1, GEN_INT (16))); addr = memory_address (Pmode, plus_constant (tramp, 6)); emit_move_insn (gen_rtx_MEM (HImode, addr), gen_lowpart (HImode, t1)); addr = memory_address (Pmode, plus_constant (tramp, 10)); emit_move_insn (gen_rtx_MEM (HImode, addr), gen_lowpart (HImode, t2)); emit_insn (gen_ashrsi3 (t2, t2, GEN_INT (16))); addr = memory_address (Pmode, plus_constant (tramp, 14)); emit_move_insn (gen_rtx_MEM (HImode, addr), gen_lowpart (HImode, t2)); } /* Emit insns to move operands[1] into operands[0]. */ void emit_pic_move (rtx *operands, enum machine_mode mode ATTRIBUTE_UNUSED) { rtx temp = reload_in_progress ? operands[0] : gen_reg_rtx (Pmode); 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, pic_offset_table_rtx); } /* Expand a move operation in mode MODE. The operands are in OPERANDS. */ void expand_move (rtx *operands, enum machine_mode mode) { if (flag_pic && SYMBOLIC_CONST (operands[1])) emit_pic_move (operands, mode); /* 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]); } /* 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 (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); rtx pat = gen_rtx_PARALLEL (VOIDmode, rtvec_alloc (sibcall ? 3 : 2)); /* 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 (flag_pic && GET_CODE (callee) == SYMBOL_REF && !SYMBOL_REF_LOCAL_P (callee)) use_reg (&use, pic_offset_table_rtx); if ((!register_no_elim_operand (callee, Pmode) && GET_CODE (callee) != SYMBOL_REF) || (GET_CODE (callee) == SYMBOL_REF && (flag_pic || 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); XVECEXP (pat, 0, 0) = call; XVECEXP (pat, 0, 1) = gen_rtx_USE (VOIDmode, cookie); if (sibcall) XVECEXP (pat, 0, 2) = gen_rtx_RETURN (VOIDmode); 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, enum machine_mode mode) { /* Allow only dregs to store value of mode HI or QI */ enum reg_class class = REGNO_REG_CLASS (regno); if (mode == CCmode) return 0; if (mode == V2HImode) return D_REGNO_P (regno); if (class == CCREGS) return mode == BImode; if (mode == PDImode) return regno == REG_A0 || regno == REG_A1; 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 (enum machine_mode mode) { return mode == V2HImode; } /* Return the cost of moving data from a register in class CLASS1 to one in class CLASS2. A cost of 2 is the default. */ int bfin_register_move_cost (enum machine_mode mode ATTRIBUTE_UNUSED, enum reg_class class1, enum reg_class class2) { /* If optimizing for size, always prefer reg-reg over reg-memory moves. */ if (optimize_size) return 2; /* There are some stalls involved when moving from a DREG to a different class reg, and using the value in one of the following instructions. Attempt to model this by slightly discouraging such moves. */ if (class1 == DREGS && class2 != DREGS) return 2 * 2; return 2; } /* Return the cost of moving data of mode M between a register and memory. A value of 2 is the default; this cost is relative to those in `REGISTER_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. */ int bfin_memory_move_cost (enum machine_mode mode ATTRIBUTE_UNUSED, enum reg_class class, int 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 (class, DPREGS)) return 10; return 8; } /* Inform reload about cases where moving X with a mode MODE to a register in CLASS requires an extra scratch register. Return the class needed for the scratch register. */ enum reg_class secondary_input_reload_class (enum reg_class class, enum machine_mode mode, rtx x) { /* 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); 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 = ! CONST_7BIT_IMM_P (INTVAL (op2)); if (class == PREGS || class == 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 (class == DREGS || class == DPREGS) return large_constant_p ? PREGS : NO_REGS; /* Reloading to anything other than a DREG? Use a PREG scratch register. */ return PREGS; } /* 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) return class == DREGS || class == AREGS ? NO_REGS : DREGS; if (class == AREGS) { if (x != const0_rtx && x_class != DREGS) return DREGS; else return NO_REGS; } /* CCREGS can only be moved from/to DREGS. */ if (class == CCREGS && x_class != DREGS) return DREGS; if (x_class == CCREGS && class != 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 (class, default_class)) return default_class; return NO_REGS; } /* Like secondary_input_reload_class; and all we do is call that function. */ enum reg_class secondary_output_reload_class (enum reg_class class, enum machine_mode mode, rtx x) { return secondary_input_reload_class (class, mode, x); } /* Implement TARGET_HANDLE_OPTION. */ static bool bfin_handle_option (size_t code, const char *arg, int value) { switch (code) { case OPT_mshared_library_id_: if (value > MAX_LIBRARY_ID) error ("-mshared-library-id=%s is not between 0 and %d", arg, MAX_LIBRARY_ID); bfin_lib_id_given = 1; return true; default: return true; } } /* Implement the macro OVERRIDE_OPTIONS. */ void override_options (void) { if (TARGET_OMIT_LEAF_FRAME_POINTER) flag_omit_frame_pointer = 1; /* Library identification */ if (bfin_lib_id_given && ! TARGET_ID_SHARED_LIBRARY) error ("-mshared-library-id= specified without -mid-shared-library"); if (TARGET_ID_SHARED_LIBRARY) /* ??? Provide a way to use a bigger GOT. */ flag_pic = 1; flag_schedule_insns = 0; } /* 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 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 = INTVAL (XEXP (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, 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, enum machine_mode mode ATTRIBUTE_UNUSED) { enum rtx_code code1, code2; rtx op0 = bfin_compare_op0, op1 = bfin_compare_op1; 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 (BImode, 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]); enum reg_class class1 = REGNO_REG_CLASS (regno); /* 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 (shifted))); 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 (val & 0xFFFF0000))); 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 (val | 0xFFFF))); } } if (D_REGNO_P (regno)) { if (CONST_7BIT_IMM_P (tmp)) { emit_insn (gen_movsi (operands[0], GEN_INT (tmp))); emit_insn (gen_movstricthi_high (operands[0], GEN_INT (val & -65536))); 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 && CONST_7BIT_IMM_P (shifted_compl)) { /* If optimizing for size, generate a sequence that has more instructions but is shorter. */ emit_insn (gen_movsi (operands[0], GEN_INT (shifted_compl))); 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 (enum 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. */ unsigned HOST_WIDE_INT mask = sz == 8 ? 0x7ffe : 0x7fff; return (v & ~(mask << shift)) == 0; } static bool bfin_valid_reg_p (unsigned int regno, int strict) { return ((strict && REGNO_OK_FOR_BASE_STRICT_P (regno)) || (!strict && REGNO_OK_FOR_BASE_NONSTRICT_P (regno))); } bool bfin_legitimate_address_p (enum machine_mode mode, rtx x, int strict) { switch (GET_CODE (x)) { case REG: if (bfin_valid_reg_p (REGNO (x), strict)) return true; break; case PLUS: if (REG_P (XEXP (x, 0)) && bfin_valid_reg_p (REGNO (XEXP (x, 0)), strict) && (GET_CODE (XEXP (x, 1)) == UNSPEC || (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)) 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)) return true; break; default: break; } return false; } static bool bfin_rtx_costs (rtx x, int code, int outer_code, int *total) { int cost2 = COSTS_N_INSNS (1); switch (code) { case CONST_INT: if (outer_code == SET || outer_code == PLUS) *total = CONST_7BIT_IMM_P (INTVAL (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: if (GET_MODE (x) == Pmode) { if (GET_CODE (XEXP (x, 0)) == MULT && GET_CODE (XEXP (XEXP (x, 0), 1)) == CONST_INT) { HOST_WIDE_INT val = INTVAL (XEXP (XEXP (x, 0), 1)); if (val == 2 || val == 4) { *total = cost2; *total += rtx_cost (XEXP (XEXP (x, 0), 0), outer_code); *total += rtx_cost (XEXP (x, 1), outer_code); return true; } } } /* fall through */ case MINUS: case ASHIFT: case ASHIFTRT: case LSHIFTRT: if (GET_MODE (x) == DImode) *total = 6 * cost2; return false; case AND: case IOR: case XOR: if (GET_MODE (x) == DImode) *total = 2 * cost2; return false; case MULT: if (GET_MODE_SIZE (GET_MODE (x)) <= UNITS_PER_WORD) *total = COSTS_N_INSNS (3); return false; default: return false; } } static void bfin_internal_label (FILE *stream, const char *prefix, unsigned long num) { fprintf (stream, "%s%s$%ld:\n", LOCAL_LABEL_PREFIX, prefix, num); } /* 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; int push_multiple_operation (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) { 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) { group = 2; first_preg_to_save = lastpreg = regno - REG_P0; } else return 0; continue; } if (group == 1) { if (regno >= REG_P0 && regno <= REG_P7) { group = 2; first_preg_to_save = lastpreg = regno - REG_P0; } else if (regno != REG_R0 + lastdreg + 1) return 0; else lastdreg++; } else if (group == 2) { if (regno != REG_P0 + lastpreg + 1) return 0; lastpreg++; } } return 1; } int pop_multiple_operation (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) { int lastdreg = 8, lastpreg = 6; int i, group; for (i = 1, group = 0; i < XVECLEN (op, 0); 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 (src) != MEM || ! REG_P (dest)) return 0; src = XEXP (src, 0); if (i == 1) { if (! REG_P (src) || REGNO (src) != REG_SP) return 0; } else if (GET_CODE (src) != PLUS || ! REG_P (XEXP (src, 0)) || REGNO (XEXP (src, 0)) != REG_SP || GET_CODE (XEXP (src, 1)) != CONST_INT || INTVAL (XEXP (src, 1)) != (i - 1) * 4) return 0; regno = REGNO (dest); if (group == 0) { if (regno == REG_R7) { group = 1; lastdreg = 7; } else if (regno != REG_P0 + lastpreg - 1) return 0; else lastpreg--; } else if (group == 1) { if (regno != REG_R0 + lastdreg - 1) return 0; else lastdreg--; } } first_dreg_to_save = lastdreg; first_preg_to_save = lastpreg; return 1; } /* Emit assembly code for one multi-register push described by INSN, with operands in OPERANDS. */ void output_push_multiple (rtx insn, rtx *operands) { char buf[80]; int ok; /* Validate the insn again, and compute first_[dp]reg_to_save. */ ok = push_multiple_operation (PATTERN (insn), VOIDmode); gcc_assert (ok); if (first_dreg_to_save == 8) sprintf (buf, "[--sp] = ( p5:%d );\n", first_preg_to_save); else if (first_preg_to_save == 6) sprintf (buf, "[--sp] = ( r7:%d );\n", first_dreg_to_save); else sprintf (buf, "[--sp] = ( r7:%d, p5:%d );\n", first_dreg_to_save, first_preg_to_save); output_asm_insn (buf, operands); } /* Emit assembly code for one multi-register pop described by INSN, with operands in OPERANDS. */ void output_pop_multiple (rtx insn, rtx *operands) { char buf[80]; int ok; /* Validate the insn again, and compute first_[dp]reg_to_save. */ ok = pop_multiple_operation (PATTERN (insn), VOIDmode); gcc_assert (ok); if (first_dreg_to_save == 8) sprintf (buf, "( p5:%d ) = [sp++];\n", first_preg_to_save); else if (first_preg_to_save == 6) sprintf (buf, "( r7:%d ) = [sp++];\n", first_dreg_to_save); else sprintf (buf, "( r7:%d, p5:%d ) = [sp++];\n", first_dreg_to_save, first_preg_to_save); output_asm_insn (buf, operands); } /* Adjust DST and SRC by OFFSET bytes, and generate one move in mode MODE. */ static void single_move_for_strmov (rtx dst, rtx src, enum machine_mode mode, HOST_WIDE_INT offset) { rtx scratch = gen_reg_rtx (mode); rtx srcmem, dstmem; srcmem = adjust_address_nv (src, mode, offset); dstmem = adjust_address_nv (dst, mode, offset); emit_move_insn (scratch, srcmem); emit_move_insn (dstmem, scratch); } /* Expand a string move operation of COUNT_EXP bytes from SRC to DST, with alignment ALIGN_EXP. Return true if successful, false if we should fall back on a different method. */ bool bfin_expand_strmov (rtx dst, rtx src, rtx count_exp, rtx align_exp) { rtx srcreg, destreg, countreg; HOST_WIDE_INT align = 0; unsigned HOST_WIDE_INT count = 0; if (GET_CODE (align_exp) == CONST_INT) align = INTVAL (align_exp); if (GET_CODE (count_exp) == CONST_INT) { count = INTVAL (count_exp); #if 0 if (!TARGET_INLINE_ALL_STRINGOPS && count > 64) return false; #endif } /* If optimizing for size, only do single copies inline. */ if (optimize_size) { if (count == 2 && align < 2) return false; if (count == 4 && align < 4) return false; if (count != 1 && count != 2 && count != 4) return false; } if (align < 2 && count != 1) return false; destreg = copy_to_mode_reg (Pmode, XEXP (dst, 0)); if (destreg != XEXP (dst, 0)) dst = replace_equiv_address_nv (dst, destreg); srcreg = copy_to_mode_reg (Pmode, XEXP (src, 0)); if (srcreg != XEXP (src, 0)) src = replace_equiv_address_nv (src, srcreg); if (count != 0 && align >= 2) { unsigned HOST_WIDE_INT offset = 0; if (align >= 4) { if ((count & ~3) == 4) { single_move_for_strmov (dst, src, SImode, offset); offset = 4; } else if (count & ~3) { HOST_WIDE_INT new_count = ((count >> 2) & 0x3fffffff) - 1; countreg = copy_to_mode_reg (Pmode, GEN_INT (new_count)); emit_insn (gen_rep_movsi (destreg, srcreg, countreg, destreg, srcreg)); } if (count & 2) { single_move_for_strmov (dst, src, HImode, offset); offset += 2; } } else { if ((count & ~1) == 2) { single_move_for_strmov (dst, src, HImode, offset); offset = 2; } else if (count & ~1) { HOST_WIDE_INT new_count = ((count >> 1) & 0x7fffffff) - 1; countreg = copy_to_mode_reg (Pmode, GEN_INT (new_count)); emit_insn (gen_rep_movhi (destreg, srcreg, countreg, destreg, srcreg)); } } if (count & 1) { single_move_for_strmov (dst, src, QImode, offset); } return true; } return false; } static int bfin_adjust_cost (rtx insn, rtx link, rtx dep_insn, int cost) { enum attr_type insn_type, dep_insn_type; int dep_insn_code_number; /* Anti and output dependencies have zero cost. */ if (REG_NOTE_KIND (link) != 0) return 0; dep_insn_code_number = recog_memoized (dep_insn); /* If we can't recognize the insns, we can't really do anything. */ if (dep_insn_code_number < 0 || recog_memoized (insn) < 0) return cost; insn_type = get_attr_type (insn); dep_insn_type = get_attr_type (dep_insn); if (dep_insn_type == TYPE_MOVE || dep_insn_type == TYPE_MCLD) { rtx pat = PATTERN (dep_insn); rtx dest = SET_DEST (pat); rtx src = SET_SRC (pat); if (! ADDRESS_REGNO_P (REGNO (dest)) || ! D_REGNO_P (REGNO (src))) return cost; return cost + (dep_insn_type == TYPE_MOVE ? 4 : 3); } return cost; } /* We use the machine specific reorg pass for emitting CSYNC instructions after conditional branches as needed. The Blackfin is unusual in that a code sequence like if cc jump label r0 = (p0) may speculatively perform the load even if the condition isn't true. This happens for a branch that is predicted not taken, because the pipeline isn't flushed or stalled, so the early stages of the following instructions, which perform the memory reference, are allowed to execute before the jump condition is evaluated. Therefore, we must insert additional instructions in all places where this could lead to incorrect behavior. The manual recommends CSYNC, while VDSP seems to use NOPs (even though its corresponding compiler option is named CSYNC). When optimizing for speed, we emit NOPs, which seems faster than a CSYNC. When optimizing for size, we turn the branch into a predicted taken one. This may be slower due to mispredicts, but saves code size. */ static void bfin_reorg (void) { rtx insn, last_condjump = NULL_RTX; int cycles_since_jump = INT_MAX; if (! TARGET_SPECLD_ANOMALY || ! TARGET_CSYNC_ANOMALY) return; /* First pass: find predicted-false branches; if something after them needs nops, insert them or change the branch to predict true. */ for (insn = get_insns (); insn; insn = NEXT_INSN (insn)) { rtx pat; if (NOTE_P (insn) || BARRIER_P (insn) || LABEL_P (insn)) continue; pat = PATTERN (insn); if (GET_CODE (pat) == USE || GET_CODE (pat) == CLOBBER || GET_CODE (pat) == ASM_INPUT || GET_CODE (pat) == ADDR_VEC || GET_CODE (pat) == ADDR_DIFF_VEC || asm_noperands (pat) >= 0) continue; if (JUMP_P (insn)) { if (any_condjump_p (insn) && ! cbranch_predicted_taken_p (insn)) { last_condjump = insn; cycles_since_jump = 0; } else cycles_since_jump = INT_MAX; } else if (INSN_P (insn)) { enum attr_type type = get_attr_type (insn); int delay_needed = 0; if (cycles_since_jump < INT_MAX) cycles_since_jump++; if (type == TYPE_MCLD && TARGET_SPECLD_ANOMALY) { rtx pat = single_set (insn); if (may_trap_p (SET_SRC (pat))) delay_needed = 3; } else if (type == TYPE_SYNC && TARGET_CSYNC_ANOMALY) delay_needed = 4; if (delay_needed > cycles_since_jump) { rtx pat; int num_clobbers; rtx *op = recog_data.operand; delay_needed -= cycles_since_jump; extract_insn (last_condjump); if (optimize_size) { pat = gen_cbranch_predicted_taken (op[0], op[1], op[2], op[3]); cycles_since_jump = INT_MAX; } else /* Do not adjust cycles_since_jump in this case, so that we'll increase the number of NOPs for a subsequent insn if necessary. */ pat = gen_cbranch_with_nops (op[0], op[1], op[2], op[3], GEN_INT (delay_needed)); PATTERN (last_condjump) = pat; INSN_CODE (last_condjump) = recog (pat, insn, &num_clobbers); } } } /* Second pass: for predicted-true branches, see if anything at the branch destination needs extra nops. */ if (! TARGET_CSYNC_ANOMALY) return; for (insn = get_insns (); insn; insn = NEXT_INSN (insn)) { if (JUMP_P (insn) && any_condjump_p (insn) && (INSN_CODE (insn) == CODE_FOR_cbranch_predicted_taken || cbranch_predicted_taken_p (insn))) { rtx target = JUMP_LABEL (insn); rtx label = target; cycles_since_jump = 0; for (; target && cycles_since_jump < 3; target = NEXT_INSN (target)) { rtx pat; if (NOTE_P (target) || BARRIER_P (target) || LABEL_P (target)) continue; pat = PATTERN (target); if (GET_CODE (pat) == USE || GET_CODE (pat) == CLOBBER || GET_CODE (pat) == ASM_INPUT || GET_CODE (pat) == ADDR_VEC || GET_CODE (pat) == ADDR_DIFF_VEC || asm_noperands (pat) >= 0) continue; if (INSN_P (target)) { enum attr_type type = get_attr_type (target); int delay_needed = 0; if (cycles_since_jump < INT_MAX) cycles_since_jump++; if (type == TYPE_SYNC && TARGET_CSYNC_ANOMALY) delay_needed = 2; if (delay_needed > cycles_since_jump) { rtx prev = prev_real_insn (label); delay_needed -= cycles_since_jump; if (dump_file) fprintf (dump_file, "Adding %d nops after %d\n", delay_needed, INSN_UID (label)); if (JUMP_P (prev) && INSN_CODE (prev) == CODE_FOR_cbranch_with_nops) { rtx x; HOST_WIDE_INT v; if (dump_file) fprintf (dump_file, "Reducing nops on insn %d.\n", INSN_UID (prev)); x = PATTERN (prev); x = XVECEXP (x, 0, 1); v = INTVAL (XVECEXP (x, 0, 0)) - delay_needed; XVECEXP (x, 0, 0) = GEN_INT (v); } while (delay_needed-- > 0) emit_insn_after (gen_nop (), label); break; } } } } } } /* Handle interrupt_handler, exception_handler and nmi_handler function attributes; arguments as in struct attribute_spec.handler. */ static tree handle_int_attribute (tree *node, tree name, tree args ATTRIBUTE_UNUSED, int flags ATTRIBUTE_UNUSED, bool *no_add_attrs) { tree x = *node; if (TREE_CODE (x) == FUNCTION_DECL) x = TREE_TYPE (x); if (TREE_CODE (x) != FUNCTION_TYPE) { warning (OPT_Wattributes, "%qs attribute only applies to functions", IDENTIFIER_POINTER (name)); *no_add_attrs = true; } else if (funkind (x) != SUBROUTINE) error ("multiple function type attributes specified"); return NULL_TREE; } /* Return 0 if the attributes for two types are incompatible, 1 if they are compatible, and 2 if they are nearly compatible (which causes a warning to be generated). */ static int bfin_comp_type_attributes (tree type1, tree type2) { e_funkind kind1, kind2; if (TREE_CODE (type1) != FUNCTION_TYPE) return 1; kind1 = funkind (type1); kind2 = funkind (type2); if (kind1 != kind2) return 0; /* Check for mismatched modifiers */ if (!lookup_attribute ("nesting", TYPE_ATTRIBUTES (type1)) != !lookup_attribute ("nesting", TYPE_ATTRIBUTES (type2))) return 0; if (!lookup_attribute ("saveall", TYPE_ATTRIBUTES (type1)) != !lookup_attribute ("saveall", TYPE_ATTRIBUTES (type2))) return 0; if (!lookup_attribute ("kspisusp", TYPE_ATTRIBUTES (type1)) != !lookup_attribute ("kspisusp", TYPE_ATTRIBUTES (type2))) return 0; if (!lookup_attribute ("longcall", TYPE_ATTRIBUTES (type1)) != !lookup_attribute ("longcall", TYPE_ATTRIBUTES (type2))) return 0; return 1; } /* Handle a "longcall" or "shortcall" attribute; arguments as in struct attribute_spec.handler. */ static tree bfin_handle_longcall_attribute (tree *node, tree name, tree args ATTRIBUTE_UNUSED, int flags ATTRIBUTE_UNUSED, bool *no_add_attrs) { if (TREE_CODE (*node) != FUNCTION_TYPE && TREE_CODE (*node) != FIELD_DECL && TREE_CODE (*node) != TYPE_DECL) { warning (OPT_Wattributes, "`%s' attribute only applies to functions", IDENTIFIER_POINTER (name)); *no_add_attrs = true; } if ((strcmp (IDENTIFIER_POINTER (name), "longcall") == 0 && lookup_attribute ("shortcall", TYPE_ATTRIBUTES (*node))) || (strcmp (IDENTIFIER_POINTER (name), "shortcall") == 0 && lookup_attribute ("longcall", TYPE_ATTRIBUTES (*node)))) { warning (OPT_Wattributes, "can't apply both longcall and shortcall attributes to the same function"); *no_add_attrs = true; } return NULL_TREE; } /* Table of valid machine attributes. */ const struct attribute_spec bfin_attribute_table[] = { /* { name, min_len, max_len, decl_req, type_req, fn_type_req, handler } */ { "interrupt_handler", 0, 0, false, true, true, handle_int_attribute }, { "exception_handler", 0, 0, false, true, true, handle_int_attribute }, { "nmi_handler", 0, 0, false, true, true, handle_int_attribute }, { "nesting", 0, 0, false, true, true, NULL }, { "kspisusp", 0, 0, false, true, true, NULL }, { "saveall", 0, 0, false, true, true, NULL }, { "longcall", 0, 0, false, true, true, bfin_handle_longcall_attribute }, { "shortcall", 0, 0, false, true, true, bfin_handle_longcall_attribute }, { NULL, 0, 0, false, false, false, NULL } }; /* Output the assembler code for a thunk function. THUNK_DECL is the declaration for the thunk function itself, FUNCTION is the decl for the target function. DELTA is an immediate constant offset to be added to THIS. If VCALL_OFFSET is nonzero, the word at *(*this + vcall_offset) should be added to THIS. */ static void bfin_output_mi_thunk (FILE *file ATTRIBUTE_UNUSED, tree thunk ATTRIBUTE_UNUSED, HOST_WIDE_INT delta, HOST_WIDE_INT vcall_offset, tree function) { rtx xops[3]; /* The this parameter is passed as the first argument. */ rtx this = gen_rtx_REG (Pmode, REG_R0); /* Adjust the this parameter by a fixed constant. */ if (delta) { xops[1] = this; if (delta >= -64 && delta <= 63) { xops[0] = GEN_INT (delta); output_asm_insn ("%1 += %0;", xops); } else if (delta >= -128 && delta < -64) { xops[0] = GEN_INT (delta + 64); output_asm_insn ("%1 += -64; %1 += %0;", xops); } else if (delta > 63 && delta <= 126) { xops[0] = GEN_INT (delta - 63); output_asm_insn ("%1 += 63; %1 += %0;", xops); } else { xops[0] = GEN_INT (delta); output_asm_insn ("r3.l = %h0; r3.h = %d0; %1 = %1 + r3;", xops); } } /* Adjust the this parameter by a value stored in the vtable. */ if (vcall_offset) { rtx p2tmp = gen_rtx_REG (Pmode, REG_P2); rtx tmp = gen_rtx_REG (Pmode, REG_R2); xops[1] = tmp; xops[2] = p2tmp; output_asm_insn ("%2 = r0; %2 = [%2];", xops); /* Adjust the this parameter. */ xops[0] = gen_rtx_MEM (Pmode, plus_constant (p2tmp, vcall_offset)); if (!memory_operand (xops[0], Pmode)) { rtx tmp2 = gen_rtx_REG (Pmode, REG_P1); xops[0] = GEN_INT (vcall_offset); xops[1] = tmp2; output_asm_insn ("%h1 = %h0; %d1 = %d0; %2 = %2 + %1", xops); xops[0] = gen_rtx_MEM (Pmode, p2tmp); } xops[2] = this; output_asm_insn ("%1 = %0; %2 = %2 + %1;", xops); } xops[0] = XEXP (DECL_RTL (function), 0); if (1 || !flag_pic || (*targetm.binds_local_p) (function)) output_asm_insn ("jump.l\t%P0", xops); } /* Codes for all the Blackfin builtins. */ enum bfin_builtins { BFIN_BUILTIN_CSYNC, BFIN_BUILTIN_SSYNC, BFIN_BUILTIN_MAX }; #define def_builtin(NAME, TYPE, CODE) \ do { \ lang_hooks.builtin_function ((NAME), (TYPE), (CODE), BUILT_IN_MD, \ NULL, NULL_TREE); \ } while (0) /* Set up all builtin functions for this target. */ static void bfin_init_builtins (void) { tree void_ftype_void = build_function_type (void_type_node, void_list_node); /* Add the remaining MMX insns with somewhat more complicated types. */ def_builtin ("__builtin_bfin_csync", void_ftype_void, BFIN_BUILTIN_CSYNC); def_builtin ("__builtin_bfin_ssync", void_ftype_void, BFIN_BUILTIN_SSYNC); } /* Expand an expression EXP that calls a built-in function, with result going to TARGET if that's convenient (and in mode MODE if that's convenient). SUBTARGET may be used as the target for computing one of EXP's operands. IGNORE is nonzero if the value is to be ignored. */ static rtx bfin_expand_builtin (tree exp, rtx target ATTRIBUTE_UNUSED, rtx subtarget ATTRIBUTE_UNUSED, enum machine_mode mode ATTRIBUTE_UNUSED, int ignore ATTRIBUTE_UNUSED) { tree fndecl = TREE_OPERAND (TREE_OPERAND (exp, 0), 0); unsigned int fcode = DECL_FUNCTION_CODE (fndecl); switch (fcode) { case BFIN_BUILTIN_CSYNC: emit_insn (gen_csync ()); return 0; case BFIN_BUILTIN_SSYNC: emit_insn (gen_ssync ()); return 0; default: gcc_unreachable (); } } #undef TARGET_INIT_BUILTINS #define TARGET_INIT_BUILTINS bfin_init_builtins #undef TARGET_EXPAND_BUILTIN #define TARGET_EXPAND_BUILTIN bfin_expand_builtin #undef TARGET_ASM_GLOBALIZE_LABEL #define TARGET_ASM_GLOBALIZE_LABEL bfin_globalize_label #undef TARGET_ASM_FILE_START #define TARGET_ASM_FILE_START output_file_start #undef TARGET_ATTRIBUTE_TABLE #define TARGET_ATTRIBUTE_TABLE bfin_attribute_table #undef TARGET_COMP_TYPE_ATTRIBUTES #define TARGET_COMP_TYPE_ATTRIBUTES bfin_comp_type_attributes #undef TARGET_RTX_COSTS #define TARGET_RTX_COSTS bfin_rtx_costs #undef TARGET_ADDRESS_COST #define TARGET_ADDRESS_COST bfin_address_cost #undef TARGET_ASM_INTERNAL_LABEL #define TARGET_ASM_INTERNAL_LABEL bfin_internal_label #undef TARGET_MACHINE_DEPENDENT_REORG #define TARGET_MACHINE_DEPENDENT_REORG bfin_reorg #undef TARGET_FUNCTION_OK_FOR_SIBCALL #define TARGET_FUNCTION_OK_FOR_SIBCALL bfin_function_ok_for_sibcall #undef TARGET_ASM_OUTPUT_MI_THUNK #define TARGET_ASM_OUTPUT_MI_THUNK bfin_output_mi_thunk #undef TARGET_ASM_CAN_OUTPUT_MI_THUNK #define TARGET_ASM_CAN_OUTPUT_MI_THUNK hook_bool_tree_hwi_hwi_tree_true #undef TARGET_SCHED_ADJUST_COST #define TARGET_SCHED_ADJUST_COST bfin_adjust_cost #undef TARGET_PROMOTE_PROTOTYPES #define TARGET_PROMOTE_PROTOTYPES hook_bool_tree_true #undef TARGET_PROMOTE_FUNCTION_ARGS #define TARGET_PROMOTE_FUNCTION_ARGS hook_bool_tree_true #undef TARGET_PROMOTE_FUNCTION_RETURN #define TARGET_PROMOTE_FUNCTION_RETURN hook_bool_tree_true #undef TARGET_ARG_PARTIAL_BYTES #define TARGET_ARG_PARTIAL_BYTES bfin_arg_partial_bytes #undef TARGET_PASS_BY_REFERENCE #define TARGET_PASS_BY_REFERENCE bfin_pass_by_reference #undef TARGET_SETUP_INCOMING_VARARGS #define TARGET_SETUP_INCOMING_VARARGS setup_incoming_varargs #undef TARGET_STRUCT_VALUE_RTX #define TARGET_STRUCT_VALUE_RTX bfin_struct_value_rtx #undef TARGET_VECTOR_MODE_SUPPORTED_P #define TARGET_VECTOR_MODE_SUPPORTED_P bfin_vector_mode_supported_p #undef TARGET_HANDLE_OPTION #define TARGET_HANDLE_OPTION bfin_handle_option #undef TARGET_DEFAULT_TARGET_FLAGS #define TARGET_DEFAULT_TARGET_FLAGS TARGET_DEFAULT struct gcc_target targetm = TARGET_INITIALIZER;