URL
https://opencores.org/ocsvn/openrisc/openrisc/trunk
Subversion Repositories openrisc
[/] [openrisc/] [trunk/] [gnu-dev/] [or1k-gcc/] [gcc/] [config/] [epiphany/] [epiphany.c] - Rev 867
Go to most recent revision | Compare with Previous | Blame | View Log
/* Subroutines used for code generation on the EPIPHANY cpu. Copyright (C) 1994, 1995, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2009, 2010, 2011 Free Software Foundation, Inc. Contributed by Embecosm on behalf of Adapteva, Inc. This file is part of GCC. GCC is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GCC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GCC; see the file COPYING3. If not see <http://www.gnu.org/licenses/>. */ #include "config.h" #include "system.h" #include "coretypes.h" #include "tm.h" #include "tree.h" #include "rtl.h" #include "regs.h" #include "hard-reg-set.h" #include "real.h" #include "insn-config.h" #include "conditions.h" #include "output.h" #include "insn-attr.h" #include "flags.h" #include "function.h" #include "expr.h" #include "diagnostic-core.h" #include "recog.h" #include "toplev.h" #include "tm_p.h" #include "target.h" #include "df.h" #include "langhooks.h" #include "insn-codes.h" #include "ggc.h" #include "tm-constrs.h" #include "tree-pass.h" #include "integrate.h" /* Which cpu we're compiling for. */ int epiphany_cpu_type; /* Name of mangle string to add to symbols to separate code compiled for each cpu (or NULL). */ const char *epiphany_mangle_cpu; /* Array of valid operand punctuation characters. */ char epiphany_punct_chars[256]; /* The rounding mode that we generally use for floating point. */ int epiphany_normal_fp_rounding; static void epiphany_init_reg_tables (void); static int get_epiphany_condition_code (rtx); static tree epiphany_handle_interrupt_attribute (tree *, tree, tree, int, bool *); static tree epiphany_handle_forwarder_attribute (tree *, tree, tree, int, bool *); static bool epiphany_pass_by_reference (cumulative_args_t, enum machine_mode, const_tree, bool); static rtx frame_insn (rtx); /* defines for the initialization of the GCC target structure. */ #define TARGET_ATTRIBUTE_TABLE epiphany_attribute_table #define TARGET_PRINT_OPERAND epiphany_print_operand #define TARGET_PRINT_OPERAND_ADDRESS epiphany_print_operand_address #define TARGET_RTX_COSTS epiphany_rtx_costs #define TARGET_ADDRESS_COST epiphany_address_cost #define TARGET_MEMORY_MOVE_COST epiphany_memory_move_cost #define TARGET_PROMOTE_FUNCTION_MODE epiphany_promote_function_mode #define TARGET_PROMOTE_PROTOTYPES hook_bool_const_tree_true #define TARGET_RETURN_IN_MEMORY epiphany_return_in_memory #define TARGET_PASS_BY_REFERENCE epiphany_pass_by_reference #define TARGET_CALLEE_COPIES hook_bool_CUMULATIVE_ARGS_mode_tree_bool_true #define TARGET_FUNCTION_VALUE epiphany_function_value #define TARGET_LIBCALL_VALUE epiphany_libcall_value #define TARGET_FUNCTION_VALUE_REGNO_P epiphany_function_value_regno_p #define TARGET_SETUP_INCOMING_VARARGS epiphany_setup_incoming_varargs /* Using the simplistic varags handling forces us to do partial reg/stack argument passing for types with larger size (> 4 bytes) than alignemnt. */ #define TARGET_ARG_PARTIAL_BYTES epiphany_arg_partial_bytes #define TARGET_FUNCTION_OK_FOR_SIBCALL epiphany_function_ok_for_sibcall #define TARGET_SCHED_ISSUE_RATE epiphany_issue_rate #define TARGET_SCHED_ADJUST_COST epiphany_adjust_cost #define TARGET_LEGITIMATE_ADDRESS_P epiphany_legitimate_address_p #define TARGET_SECONDARY_RELOAD epiphany_secondary_reload #define TARGET_OPTION_OVERRIDE epiphany_override_options #define TARGET_CONDITIONAL_REGISTER_USAGE epiphany_conditional_register_usage #define TARGET_FUNCTION_ARG epiphany_function_arg #define TARGET_FUNCTION_ARG_ADVANCE epiphany_function_arg_advance #define TARGET_FUNCTION_ARG_BOUNDARY epiphany_function_arg_boundary #define TARGET_TRAMPOLINE_INIT epiphany_trampoline_init /* Nonzero if the constant rtx value is a legitimate general operand. We can handle any 32- or 64-bit constant. */ #define TARGET_LEGITIMATE_CONSTANT_P hook_bool_mode_rtx_true #define TARGET_MIN_DIVISIONS_FOR_RECIP_MUL \ epiphany_min_divisions_for_recip_mul #define TARGET_VECTORIZE_PREFERRED_SIMD_MODE epiphany_preferred_simd_mode #define TARGET_VECTOR_MODE_SUPPORTED_P epiphany_vector_mode_supported_p #define TARGET_VECTORIZE_VECTOR_ALIGNMENT_REACHABLE \ epiphany_vector_alignment_reachable #define TARGET_VECTORIZE_SUPPORT_VECTOR_MISALIGNMENT \ epiphany_support_vector_misalignment #define TARGET_ASM_CAN_OUTPUT_MI_THUNK \ hook_bool_const_tree_hwi_hwi_const_tree_true #define TARGET_ASM_OUTPUT_MI_THUNK epiphany_output_mi_thunk #include "target-def.h" #undef TARGET_ASM_ALIGNED_HI_OP #define TARGET_ASM_ALIGNED_HI_OP "\t.hword\t" #undef TARGET_ASM_ALIGNED_SI_OP #define TARGET_ASM_ALIGNED_SI_OP "\t.word\t" bool epiphany_is_interrupt_p (tree decl) { tree attrs; attrs = DECL_ATTRIBUTES (decl); if (lookup_attribute ("interrupt", attrs)) return true; else return false; } /* Called from epiphany_override_options. We use this to initialize various things. */ static void epiphany_init (void) { /* N.B. this pass must not run before the first optimize_mode_switching pass because of the side offect of epiphany_mode_needed on MACHINE_FUNCTION(cfun)->unknown_mode_uses. But it must run before pass_resolve_sw_modes. */ static struct register_pass_info insert_use_info = { &pass_mode_switch_use.pass, "mode_sw", 1, PASS_POS_INSERT_AFTER }; static struct register_pass_info mode_sw2_info = { &pass_mode_switching.pass, "mode_sw", 1, PASS_POS_INSERT_AFTER }; static struct register_pass_info mode_sw3_info = { &pass_resolve_sw_modes.pass, "mode_sw", 1, PASS_POS_INSERT_AFTER }; static struct register_pass_info mode_sw4_info = { &pass_split_all_insns.pass, "mode_sw", 1, PASS_POS_INSERT_AFTER }; epiphany_init_reg_tables (); /* Initialize array for PRINT_OPERAND_PUNCT_VALID_P. */ memset (epiphany_punct_chars, 0, sizeof (epiphany_punct_chars)); epiphany_punct_chars['-'] = 1; epiphany_normal_fp_rounding = (epiphany_normal_fp_mode == FP_MODE_ROUND_TRUNC ? FP_MODE_ROUND_TRUNC : FP_MODE_ROUND_NEAREST); register_pass (&mode_sw4_info); register_pass (&mode_sw2_info); register_pass (&mode_sw3_info); register_pass (&insert_use_info); register_pass (&mode_sw2_info); #if 1 /* As long as peep2_rescan is not implemented, (see http://gcc.gnu.org/ml/gcc-patches/2011-10/msg02819.html,) we need a second peephole2 pass to get reasonable code. */ { static struct register_pass_info peep2_2_info = { &pass_peephole2.pass, "peephole2", 1, PASS_POS_INSERT_AFTER }; register_pass (&peep2_2_info); } #endif } /* The condition codes of the EPIPHANY, and the inverse function. */ static const char *const epiphany_condition_codes[] = { /* 0 1 2 3 4 5 6 7 8 9 */ "eq", "ne", "ltu", "gteu", "gt", "lte", "gte", "lt", "gtu", "lteu", /* 10 11 12 13 */ "beq","bne","blt", "blte", }; #define EPIPHANY_INVERSE_CONDITION_CODE(X) ((X) ^ 1) /* Returns the index of the EPIPHANY condition code string in `epiphany_condition_codes'. COMPARISON should be an rtx like `(eq (...) (...))'. */ static int get_epiphany_condition_code (rtx comparison) { switch (GET_MODE (XEXP (comparison, 0))) { case CCmode: switch (GET_CODE (comparison)) { case EQ : return 0; case NE : return 1; case LTU : return 2; case GEU : return 3; case GT : return 4; case LE : return 5; case GE : return 6; case LT : return 7; case GTU : return 8; case LEU : return 9; default : gcc_unreachable (); } case CC_N_NEmode: switch (GET_CODE (comparison)) { case EQ: return 6; case NE: return 7; default: gcc_unreachable (); } case CC_C_LTUmode: switch (GET_CODE (comparison)) { case GEU: return 2; case LTU: return 3; default: gcc_unreachable (); } case CC_C_GTUmode: switch (GET_CODE (comparison)) { case LEU: return 3; case GTU: return 2; default: gcc_unreachable (); } case CC_FPmode: switch (GET_CODE (comparison)) { case EQ: return 10; case NE: return 11; case LT: return 12; case LE: return 13; default: gcc_unreachable (); } case CC_FP_EQmode: switch (GET_CODE (comparison)) { case EQ: return 0; case NE: return 1; default: gcc_unreachable (); } case CC_FP_GTEmode: switch (GET_CODE (comparison)) { case EQ: return 0; case NE: return 1; case GT : return 4; case GE : return 6; case UNLE : return 5; case UNLT : return 7; default: gcc_unreachable (); } case CC_FP_ORDmode: switch (GET_CODE (comparison)) { case ORDERED: return 9; case UNORDERED: return 8; default: gcc_unreachable (); } case CC_FP_UNEQmode: switch (GET_CODE (comparison)) { case UNEQ: return 9; case LTGT: return 8; default: gcc_unreachable (); } default: gcc_unreachable (); } /*NOTREACHED*/ return (42); } /* 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) { if (GET_MODE_SIZE (mode) > UNITS_PER_WORD) return (regno & 1) == 0 && GPR_P (regno); else return 1; } /* Given a comparison code (EQ, NE, etc.) and the first operand of a COMPARE, return the mode to be used for the comparison. */ enum machine_mode epiphany_select_cc_mode (enum rtx_code op, rtx x ATTRIBUTE_UNUSED, rtx y ATTRIBUTE_UNUSED) { if (GET_MODE_CLASS (GET_MODE (x)) == MODE_FLOAT) { if (TARGET_SOFT_CMPSF) { if (op == EQ || op == NE) return CC_FP_EQmode; if (op == ORDERED || op == UNORDERED) return CC_FP_ORDmode; if (op == UNEQ || op == LTGT) return CC_FP_UNEQmode; return CC_FP_GTEmode; } return CC_FPmode; } /* recognize combiner pattern ashlsi_btst: (parallel [ (set (reg:N_NE 65 cc1) (compare:N_NE (zero_extract:SI (reg/v:SI 75 [ a ]) (const_int 1 [0x1]) (const_int 0 [0x0])) (const_int 0 [0x0]))) (clobber (scratch:SI)) */ else if ((op == EQ || op == NE) && GET_CODE (x) == ZERO_EXTRACT && XEXP (x, 1) == const1_rtx && CONST_INT_P (XEXP (x, 2))) return CC_N_NEmode; else if ((op == GEU || op == LTU) && GET_CODE (x) == PLUS) return CC_C_LTUmode; else if ((op == LEU || op == GTU) && GET_CODE (x) == MINUS) return CC_C_GTUmode; else return CCmode; } enum reg_class epiphany_regno_reg_class[FIRST_PSEUDO_REGISTER]; static void epiphany_init_reg_tables (void) { int i; for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) { if (i == GPR_LR) epiphany_regno_reg_class[i] = LR_REGS; else if (i <= 7 && TARGET_PREFER_SHORT_INSN_REGS) epiphany_regno_reg_class[i] = SHORT_INSN_REGS; else if (call_used_regs[i] && TEST_HARD_REG_BIT (reg_class_contents[GENERAL_REGS], i)) epiphany_regno_reg_class[i] = SIBCALL_REGS; else if (i >= CORE_CONTROL_FIRST && i <= CORE_CONTROL_LAST) epiphany_regno_reg_class[i] = CORE_CONTROL_REGS; else if (i < (GPR_LAST+1) || i == ARG_POINTER_REGNUM || i == FRAME_POINTER_REGNUM) epiphany_regno_reg_class[i] = GENERAL_REGS; else if (i == CC_REGNUM) epiphany_regno_reg_class[i] = NO_REGS /* CC_REG: must be NO_REGS */; else epiphany_regno_reg_class[i] = NO_REGS; } } /* EPIPHANY specific attribute support. The EPIPHANY has these attributes: interrupt - for interrupt functions. short_call - the function is assumed to be reachable with the b / bl instructions. long_call - the function address is loaded into a register before use. disinterrupt - functions which mask interrupts throughout. They unmask them while calling an interruptible function, though. */ static const struct attribute_spec epiphany_attribute_table[] = { /* { name, min_len, max_len, decl_req, type_req, fn_type_req, handler } */ { "interrupt", 0, 9, true, false, false, epiphany_handle_interrupt_attribute, true }, { "forwarder_section", 1, 1, true, false, false, epiphany_handle_forwarder_attribute, false }, { "long_call", 0, 0, false, true, true, NULL, false }, { "short_call", 0, 0, false, true, true, NULL, false }, { "disinterrupt", 0, 0, false, true, true, NULL, true }, { NULL, 0, 0, false, false, false, NULL, false } }; /* Handle an "interrupt" attribute; arguments as in struct attribute_spec.handler. */ static tree epiphany_handle_interrupt_attribute (tree *node ATTRIBUTE_UNUSED, tree name, tree args, int flags ATTRIBUTE_UNUSED, bool *no_add_attrs) { tree value; if (!args) return NULL_TREE; value = TREE_VALUE (args); if (TREE_CODE (value) != STRING_CST) { warning (OPT_Wattributes, "argument of %qE attribute is not a string constant", name); *no_add_attrs = true; } else if (strcmp (TREE_STRING_POINTER (value), "reset") && strcmp (TREE_STRING_POINTER (value), "software_exception") && strcmp (TREE_STRING_POINTER (value), "page_miss") && strcmp (TREE_STRING_POINTER (value), "timer0") && strcmp (TREE_STRING_POINTER (value), "timer1") && strcmp (TREE_STRING_POINTER (value), "message") && strcmp (TREE_STRING_POINTER (value), "dma0") && strcmp (TREE_STRING_POINTER (value), "dma1") && strcmp (TREE_STRING_POINTER (value), "wand") && strcmp (TREE_STRING_POINTER (value), "swi")) { warning (OPT_Wattributes, "argument of %qE attribute is not \"reset\", \"software_exception\", \"page_miss\", \"timer0\", \"timer1\", \"message\", \"dma0\", \"dma1\", \"wand\" or \"swi\"", name); *no_add_attrs = true; return NULL_TREE; } return epiphany_handle_interrupt_attribute (node, name, TREE_CHAIN (args), flags, no_add_attrs); } /* Handle a "forwarder_section" attribute; arguments as in struct attribute_spec.handler. */ static tree epiphany_handle_forwarder_attribute (tree *node ATTRIBUTE_UNUSED, tree name, tree args, int flags ATTRIBUTE_UNUSED, bool *no_add_attrs) { tree value; value = TREE_VALUE (args); if (TREE_CODE (value) != STRING_CST) { warning (OPT_Wattributes, "argument of %qE attribute is not a string constant", name); *no_add_attrs = true; } return NULL_TREE; } /* Misc. utilities. */ /* Generate a SYMBOL_REF for the special function NAME. When the address can't be placed directly into a call instruction, and if possible, copy it to a register so that cse / code hoisting is possible. */ rtx sfunc_symbol (const char *name) { rtx sym = gen_rtx_SYMBOL_REF (Pmode, name); /* These sfuncs should be hidden, and every dso should get a copy. */ SYMBOL_REF_FLAGS (sym) = SYMBOL_FLAG_FUNCTION | SYMBOL_FLAG_LOCAL; if (TARGET_SHORT_CALLS) ; /* Nothing to be done. */ else if (can_create_pseudo_p ()) sym = copy_to_mode_reg (Pmode, sym); else /* We rely on reload to fix this up. */ gcc_assert (!reload_in_progress || reload_completed); return sym; } /* X and Y are two things to compare using CODE in IN_MODE. Emit the compare insn, construct the the proper cc reg in the proper mode, and return the rtx for the cc reg comparison in CMODE. */ rtx gen_compare_reg (enum machine_mode cmode, enum rtx_code code, enum machine_mode in_mode, rtx x, rtx y) { enum machine_mode mode = SELECT_CC_MODE (code, x, y); rtx cc_reg, pat, clob0, clob1, clob2; if (in_mode == VOIDmode) in_mode = GET_MODE (x); if (in_mode == VOIDmode) in_mode = GET_MODE (y); if (mode == CC_FPmode) { /* The epiphany has only EQ / NE / LT / LE conditions for hardware floating point. */ if (code == GT || code == GE || code == UNLE || code == UNLT) { rtx tmp = x; x = y; y = tmp; code = swap_condition (code); } cc_reg = gen_rtx_REG (mode, CCFP_REGNUM); y = force_reg (in_mode, y); } else { if (mode == CC_FP_GTEmode && (code == LE || code == LT || code == UNGT || code == UNGE)) { rtx tmp = x; x = y; y = tmp; code = swap_condition (code); } cc_reg = gen_rtx_REG (mode, CC_REGNUM); } if ((mode == CC_FP_EQmode || mode == CC_FP_GTEmode || mode == CC_FP_ORDmode || mode == CC_FP_UNEQmode) /* mov<mode>cc might want to re-emit a comparison during ifcvt. */ && (!REG_P (x) || REGNO (x) != 0 || !REG_P (y) || REGNO (y) != 1)) { rtx reg; gcc_assert (currently_expanding_to_rtl); reg = gen_rtx_REG (in_mode, 0); gcc_assert (!reg_overlap_mentioned_p (reg, y)); emit_move_insn (reg, x); x = reg; reg = gen_rtx_REG (in_mode, 1); emit_move_insn (reg, y); y = reg; } else x = force_reg (in_mode, x); pat = gen_rtx_SET (VOIDmode, cc_reg, gen_rtx_COMPARE (mode, x, y)); if (mode == CC_FP_EQmode || mode == CC_FP_GTEmode) { const char *name = mode == CC_FP_EQmode ? "__eqsf2" : "__gtesf2"; rtx use = gen_rtx_USE (VOIDmode, sfunc_symbol (name)); clob0 = gen_rtx_CLOBBER (VOIDmode, gen_rtx_REG (SImode, GPR_IP)); clob1 = gen_rtx_CLOBBER (VOIDmode, gen_rtx_REG (SImode, GPR_LR)); pat = gen_rtx_PARALLEL (VOIDmode, gen_rtvec (4, pat, use, clob0, clob1)); } else if (mode == CC_FP_ORDmode || mode == CC_FP_UNEQmode) { const char *name = mode == CC_FP_ORDmode ? "__ordsf2" : "__uneqsf2"; rtx use = gen_rtx_USE (VOIDmode, sfunc_symbol (name)); clob0 = gen_rtx_CLOBBER (VOIDmode, gen_rtx_REG (SImode, GPR_IP)); clob1 = gen_rtx_CLOBBER (VOIDmode, gen_rtx_REG (SImode, GPR_16)); clob2 = gen_rtx_CLOBBER (VOIDmode, gen_rtx_REG (SImode, GPR_LR)); pat = gen_rtx_PARALLEL (VOIDmode, gen_rtvec (5, pat, use, clob0, clob1, clob2)); } else { clob0 = gen_rtx_CLOBBER (VOIDmode, gen_rtx_SCRATCH (in_mode)); pat = gen_rtx_PARALLEL (VOIDmode, gen_rtvec (2, pat, clob0)); } emit_insn (pat); return gen_rtx_fmt_ee (code, cmode, cc_reg, const0_rtx); } /* The ROUND_ADVANCE* macros are local to this file. */ /* Round SIZE up to a word boundary. */ #define ROUND_ADVANCE(SIZE) \ (((SIZE) + UNITS_PER_WORD - 1) / UNITS_PER_WORD) /* Round arg MODE/TYPE up to the next word boundary. */ #define ROUND_ADVANCE_ARG(MODE, TYPE) \ ((MODE) == BLKmode \ ? ROUND_ADVANCE (int_size_in_bytes (TYPE)) \ : ROUND_ADVANCE (GET_MODE_SIZE (MODE))) /* Round CUM up to the necessary point for argument MODE/TYPE. */ #define ROUND_ADVANCE_CUM(CUM, MODE, TYPE) \ (epiphany_function_arg_boundary ((MODE), (TYPE)) > BITS_PER_WORD \ ? (((CUM) + 1) & ~1) \ : (CUM)) static unsigned int epiphany_function_arg_boundary (enum machine_mode mode, const_tree type) { if ((type ? TYPE_ALIGN (type) : GET_MODE_BITSIZE (mode)) <= PARM_BOUNDARY) return PARM_BOUNDARY; return 2 * PARM_BOUNDARY; } /* Do any needed setup for a variadic function. For the EPIPHANY, we actually emit the code in epiphany_expand_prologue. CUM has not been updated for the last named argument which has type TYPE and mode MODE, and we rely on this fact. */ static void epiphany_setup_incoming_varargs (cumulative_args_t cum, enum machine_mode mode, tree type, int *pretend_size, int no_rtl) { int first_anon_arg; CUMULATIVE_ARGS next_cum; machine_function_t *mf = MACHINE_FUNCTION (cfun); /* All BLKmode values are passed by reference. */ gcc_assert (mode != BLKmode); next_cum = *get_cumulative_args (cum); next_cum = ROUND_ADVANCE_CUM (next_cum, mode, type) + ROUND_ADVANCE_ARG (mode, type); first_anon_arg = next_cum; if (first_anon_arg < MAX_EPIPHANY_PARM_REGS && !no_rtl) { /* Note that first_reg_offset < MAX_EPIPHANY_PARM_REGS. */ int first_reg_offset = first_anon_arg; *pretend_size = ((MAX_EPIPHANY_PARM_REGS - first_reg_offset) * UNITS_PER_WORD); } mf->args_parsed = 1; mf->pretend_args_odd = ((*pretend_size & UNITS_PER_WORD) ? 1 : 0); } static int epiphany_arg_partial_bytes (cumulative_args_t cum, enum machine_mode mode, tree type, bool named ATTRIBUTE_UNUSED) { int words = 0, rounded_cum; gcc_assert (!epiphany_pass_by_reference (cum, mode, type, /* named */ true)); rounded_cum = ROUND_ADVANCE_CUM (*get_cumulative_args (cum), mode, type); if (rounded_cum < MAX_EPIPHANY_PARM_REGS) { words = MAX_EPIPHANY_PARM_REGS - rounded_cum; if (words >= ROUND_ADVANCE_ARG (mode, type)) words = 0; } return words * UNITS_PER_WORD; } /* Cost functions. */ /* Compute a (partial) cost for rtx X. Return true if the complete cost has been computed, and false if subexpressions should be scanned. In either case, *TOTAL contains the cost result. */ static bool epiphany_rtx_costs (rtx x, int code, int outer_code, int opno ATTRIBUTE_UNUSED, int *total, bool speed ATTRIBUTE_UNUSED) { switch (code) { /* Small integers in the right context are as cheap as registers. */ case CONST_INT: if ((outer_code == PLUS || outer_code == MINUS) && SIMM11 (INTVAL (x))) { *total = 0; return true; } if (IMM16 (INTVAL (x))) { *total = outer_code == SET ? 0 : COSTS_N_INSNS (1); return true; } /* FALLTHRU */ case CONST: case LABEL_REF: case SYMBOL_REF: *total = COSTS_N_INSNS ((epiphany_small16 (x) ? 0 : 1) + (outer_code == SET ? 0 : 1)); return true; case CONST_DOUBLE: { rtx high, low; split_double (x, &high, &low); *total = COSTS_N_INSNS (!IMM16 (INTVAL (high)) + !IMM16 (INTVAL (low))); return true; } case ASHIFT: case ASHIFTRT: case LSHIFTRT: *total = COSTS_N_INSNS (1); return true; default: return false; } } /* Provide the costs of an addressing mode that contains ADDR. If ADDR is not a valid address, its cost is irrelevant. */ static int epiphany_address_cost (rtx addr, bool speed) { rtx reg; rtx off = const0_rtx; int i; if (speed) return 0; /* Return 0 for addresses valid in short insns, 1 for addresses only valid in long insns. */ switch (GET_CODE (addr)) { case PLUS : reg = XEXP (addr, 0); off = XEXP (addr, 1); break; case POST_MODIFY: reg = XEXP (addr, 0); off = XEXP (addr, 1); gcc_assert (GET_CODE (off) == PLUS && rtx_equal_p (reg, XEXP (off, 0))); off = XEXP (off, 1); if (satisfies_constraint_Rgs (reg) && satisfies_constraint_Rgs (off)) return 0; return 1; case REG: default: reg = addr; break; } if (!satisfies_constraint_Rgs (reg)) return 1; /* ??? We don't know the mode of the memory access. We are going to assume SImode, unless lack of offset alignment indicates a smaller access. */ /* First, make sure we have a valid integer. */ if (!satisfies_constraint_L (off)) return 1; i = INTVAL (off); if ((i & 1) == 0) i >>= 1; if ((i & 1) == 0) i >>= 1; if (i < -7 || i > 7) return 1; return 0; } /* Compute the cost of moving data between registers and memory. For integer, load latency is twice as long as register-register moves, but issue pich is the same. For floating point, load latency is three times as much as a reg-reg move. */ static int epiphany_memory_move_cost (enum machine_mode mode, reg_class_t rclass ATTRIBUTE_UNUSED, bool in ATTRIBUTE_UNUSED) { return GET_MODE_CLASS (mode) == MODE_INT ? 3 : 4; } /* Function prologue/epilogue handlers. */ /* EPIPHANY stack frames look like: Before call After call +-----------------------+ +-----------------------+ | | | | high | local variables, | | local variables, | mem | reg save area, etc. | | reg save area, etc. | | | | | +-----------------------+ +-----------------------+ | | | | | arguments on stack. | | arguments on stack. | | | | | SP+8->+-----------------------+FP+8m->+-----------------------+ | 2 word save area for | | reg parm save area, | | leaf funcs / flags | | only created for | SP+0->+-----------------------+ | variable argument | | functions | FP+8n->+-----------------------+ | | | register save area | | | +-----------------------+ | | | local variables | | | FP+0->+-----------------------+ | | | alloca allocations | | | +-----------------------+ | | | arguments on stack | | | SP+8->+-----------------------+ low | 2 word save area for | memory | leaf funcs / flags | SP+0->+-----------------------+ Notes: 1) The "reg parm save area" does not exist for non variable argument fns. The "reg parm save area" could be eliminated if we created our own TARGET_GIMPLIFY_VA_ARG_EXPR, but that has tradeoffs as well (so it's not done). */ /* Structure to be filled in by epiphany_compute_frame_size with register save masks, and offsets for the current function. */ struct epiphany_frame_info { unsigned int total_size; /* # bytes that the entire frame takes up. */ unsigned int pretend_size; /* # bytes we push and pretend caller did. */ unsigned int args_size; /* # bytes that outgoing arguments take up. */ unsigned int reg_size; /* # bytes needed to store regs. */ unsigned int var_size; /* # bytes that variables take up. */ HARD_REG_SET gmask; /* Set of saved gp registers. */ int initialized; /* Nonzero if frame size already calculated. */ int stld_sz; /* Current load/store data size for offset adjustment. */ int need_fp; /* value to override "frame_pointer_needed */ int first_slot, last_slot, first_slot_offset, last_slot_offset; int first_slot_size; int small_threshold; }; /* Current frame information calculated by epiphany_compute_frame_size. */ static struct epiphany_frame_info current_frame_info; /* Zero structure to initialize current_frame_info. */ static struct epiphany_frame_info zero_frame_info; /* The usual; we set up our machine_function data. */ static struct machine_function * epiphany_init_machine_status (void) { struct machine_function *machine; /* Reset state info for each function. */ current_frame_info = zero_frame_info; machine = ggc_alloc_cleared_machine_function_t (); return machine; } /* Implements INIT_EXPANDERS. We just set up to call the above * function. */ void epiphany_init_expanders (void) { init_machine_status = epiphany_init_machine_status; } /* Type of function DECL. The result is cached. To reset the cache at the end of a function, call with DECL = NULL_TREE. */ static enum epiphany_function_type epiphany_compute_function_type (tree decl) { tree a; /* Cached value. */ static enum epiphany_function_type fn_type = EPIPHANY_FUNCTION_UNKNOWN; /* Last function we were called for. */ static tree last_fn = NULL_TREE; /* Resetting the cached value? */ if (decl == NULL_TREE) { fn_type = EPIPHANY_FUNCTION_UNKNOWN; last_fn = NULL_TREE; return fn_type; } if (decl == last_fn && fn_type != EPIPHANY_FUNCTION_UNKNOWN) return fn_type; /* Assume we have a normal function (not an interrupt handler). */ fn_type = EPIPHANY_FUNCTION_NORMAL; /* Now see if this is an interrupt handler. */ for (a = DECL_ATTRIBUTES (decl); a; a = TREE_CHAIN (a)) { tree name = TREE_PURPOSE (a); if (name == get_identifier ("interrupt")) fn_type = EPIPHANY_FUNCTION_INTERRUPT; } last_fn = decl; return fn_type; } #define RETURN_ADDR_REGNUM GPR_LR #define FRAME_POINTER_MASK (1 << (FRAME_POINTER_REGNUM)) #define RETURN_ADDR_MASK (1 << (RETURN_ADDR_REGNUM)) /* Tell prologue and epilogue if register REGNO should be saved / restored. The return address and frame pointer are treated separately. Don't consider them here. */ #define MUST_SAVE_REGISTER(regno, interrupt_p) \ ((df_regs_ever_live_p (regno) \ || (interrupt_p && !current_function_is_leaf \ && call_used_regs[regno] && !fixed_regs[regno])) \ && (!call_used_regs[regno] || regno == GPR_LR \ || (interrupt_p && regno != GPR_SP))) #define MUST_SAVE_RETURN_ADDR 0 /* Return the bytes needed to compute the frame pointer from the current stack pointer. SIZE is the size needed for local variables. */ static unsigned int epiphany_compute_frame_size (int size /* # of var. bytes allocated. */) { int regno; unsigned int total_size, var_size, args_size, pretend_size, reg_size; HARD_REG_SET gmask; enum epiphany_function_type fn_type; int interrupt_p; int first_slot, last_slot, first_slot_offset, last_slot_offset; int first_slot_size; int small_slots = 0; long lr_slot_offset; var_size = size; args_size = crtl->outgoing_args_size; pretend_size = crtl->args.pretend_args_size; total_size = args_size + var_size; reg_size = 0; CLEAR_HARD_REG_SET (gmask); first_slot = -1; first_slot_offset = 0; last_slot = -1; last_slot_offset = 0; first_slot_size = UNITS_PER_WORD; /* See if this is an interrupt handler. Call used registers must be saved for them too. */ fn_type = epiphany_compute_function_type (current_function_decl); interrupt_p = EPIPHANY_INTERRUPT_P (fn_type); /* Calculate space needed for registers. */ for (regno = MAX_EPIPHANY_PARM_REGS - 1; pretend_size > reg_size; regno--) { reg_size += UNITS_PER_WORD; SET_HARD_REG_BIT (gmask, regno); if (epiphany_stack_offset - reg_size == 0) first_slot = regno; } if (interrupt_p) reg_size += 2 * UNITS_PER_WORD; else small_slots = epiphany_stack_offset / UNITS_PER_WORD; if (frame_pointer_needed) { current_frame_info.need_fp = 1; if (!interrupt_p && first_slot < 0) first_slot = GPR_FP; } else current_frame_info.need_fp = 0; for (regno = 0; regno <= GPR_LAST; regno++) { if (MUST_SAVE_REGISTER (regno, interrupt_p)) { gcc_assert (!TEST_HARD_REG_BIT (gmask, regno)); reg_size += UNITS_PER_WORD; SET_HARD_REG_BIT (gmask, regno); /* FIXME: when optimizing for speed, take schedling into account when selecting these registers. */ if (regno == first_slot) gcc_assert (regno == GPR_FP && frame_pointer_needed); else if (!interrupt_p && first_slot < 0) first_slot = regno; else if (last_slot < 0 && (first_slot ^ regno) != 1 && (!interrupt_p || regno > GPR_0 + 1)) last_slot = regno; } } if (TEST_HARD_REG_BIT (gmask, GPR_LR)) MACHINE_FUNCTION (cfun)->lr_clobbered = 1; /* ??? Could sometimes do better than that. */ current_frame_info.small_threshold = (optimize >= 3 || interrupt_p ? 0 : pretend_size ? small_slots : 4 + small_slots - (first_slot == GPR_FP)); /* If there might be variables with 64-bit alignment requirement, align the start of the variables. */ if (var_size >= 2 * UNITS_PER_WORD /* We don't want to split a double reg save/restore across two unpaired stack slots when optimizing. This rounding could be avoided with more complex reordering of the register saves, but that would seem to be a lot of code complexity for little gain. */ || (reg_size > 8 && optimize)) reg_size = EPIPHANY_STACK_ALIGN (reg_size); if (total_size + reg_size <= (unsigned) epiphany_stack_offset && !interrupt_p && current_function_is_leaf && !frame_pointer_needed) { first_slot = -1; last_slot = -1; goto alloc_done; } else if (reg_size && !interrupt_p && reg_size < (unsigned HOST_WIDE_INT) epiphany_stack_offset) reg_size = epiphany_stack_offset; if (interrupt_p) { if (total_size + reg_size < 0x3fc) { first_slot_offset = EPIPHANY_STACK_ALIGN (total_size + reg_size); first_slot_offset += EPIPHANY_STACK_ALIGN (epiphany_stack_offset); last_slot = -1; } else { first_slot_offset = EPIPHANY_STACK_ALIGN (reg_size); last_slot_offset = EPIPHANY_STACK_ALIGN (total_size); last_slot_offset += EPIPHANY_STACK_ALIGN (epiphany_stack_offset); if (last_slot >= 0) CLEAR_HARD_REG_BIT (gmask, last_slot); } } else if (total_size + reg_size < 0x1ffc && first_slot >= 0) { first_slot_offset = EPIPHANY_STACK_ALIGN (total_size + reg_size); last_slot = -1; } else { if (total_size + reg_size <= (unsigned) epiphany_stack_offset) { gcc_assert (first_slot < 0); gcc_assert (reg_size == 0); last_slot_offset = EPIPHANY_STACK_ALIGN (total_size + reg_size); } else { first_slot_offset = (reg_size ? EPIPHANY_STACK_ALIGN (reg_size - epiphany_stack_offset) : 0); if (!first_slot_offset) { if (first_slot != GPR_FP || !current_frame_info.need_fp) last_slot = first_slot; first_slot = -1; } last_slot_offset = EPIPHANY_STACK_ALIGN (total_size); if (reg_size) last_slot_offset += EPIPHANY_STACK_ALIGN (epiphany_stack_offset); } if (last_slot >= 0) CLEAR_HARD_REG_BIT (gmask, last_slot); } alloc_done: if (first_slot >= 0) { CLEAR_HARD_REG_BIT (gmask, first_slot); if (TEST_HARD_REG_BIT (gmask, first_slot ^ 1) && epiphany_stack_offset - pretend_size >= 2 * UNITS_PER_WORD) { CLEAR_HARD_REG_BIT (gmask, first_slot ^ 1); first_slot_size = 2 * UNITS_PER_WORD; first_slot &= ~1; } } total_size = first_slot_offset + last_slot_offset; lr_slot_offset = (frame_pointer_needed ? first_slot_offset : (long) total_size); if (first_slot != GPR_LR) { int stack_offset = epiphany_stack_offset - UNITS_PER_WORD; for (regno = 0; ; regno++) { if (stack_offset + UNITS_PER_WORD - first_slot_size == 0 && first_slot >= 0) { stack_offset -= first_slot_size; regno--; } else if (regno == GPR_LR) break; else if TEST_HARD_REG_BIT (gmask, regno) stack_offset -= UNITS_PER_WORD; } lr_slot_offset += stack_offset; } /* Save computed information. */ current_frame_info.total_size = total_size; current_frame_info.pretend_size = pretend_size; current_frame_info.var_size = var_size; current_frame_info.args_size = args_size; current_frame_info.reg_size = reg_size; COPY_HARD_REG_SET (current_frame_info.gmask, gmask); current_frame_info.first_slot = first_slot; current_frame_info.last_slot = last_slot; current_frame_info.first_slot_offset = first_slot_offset; current_frame_info.first_slot_size = first_slot_size; current_frame_info.last_slot_offset = last_slot_offset; MACHINE_FUNCTION (cfun)->lr_slot_offset = lr_slot_offset; current_frame_info.initialized = reload_completed; /* Ok, we're done. */ return total_size; } /* Print operand X (an rtx) in assembler syntax to file FILE. CODE is a letter or dot (`z' in `%z0') or 0 if no letter was specified. For `%' followed by punctuation, CODE is the punctuation and X is null. */ static void epiphany_print_operand (FILE *file, rtx x, int code) { switch (code) { case 'd': fputs (epiphany_condition_codes[get_epiphany_condition_code (x)], file); return; case 'D': fputs (epiphany_condition_codes[EPIPHANY_INVERSE_CONDITION_CODE (get_epiphany_condition_code (x))], file); return; case 'X': current_frame_info.stld_sz = 8; break; case 'C' : current_frame_info.stld_sz = 4; break; case 'c' : current_frame_info.stld_sz = 2; break; case 'f': fputs (REG_P (x) ? "jalr " : "bl ", file); break; case '-': fprintf (file, "r%d", epiphany_m1reg); return; case 0 : /* Do nothing special. */ break; default : /* Unknown flag. */ output_operand_lossage ("invalid operand output code"); } switch (GET_CODE (x)) { rtx addr; rtx offset; case REG : fputs (reg_names[REGNO (x)], file); break; case MEM : if (code == 0) current_frame_info.stld_sz = 1; fputc ('[', file); addr = XEXP (x, 0); switch (GET_CODE (addr)) { case POST_INC: offset = GEN_INT (GET_MODE_SIZE (GET_MODE (x))); addr = XEXP (addr, 0); break; case POST_DEC: offset = GEN_INT (-GET_MODE_SIZE (GET_MODE (x))); addr = XEXP (addr, 0); break; case POST_MODIFY: offset = XEXP (XEXP (addr, 1), 1); addr = XEXP (addr, 0); break; default: offset = 0; break; } output_address (addr); fputc (']', file); if (offset) { fputc (',', file); if (CONST_INT_P (offset)) switch (GET_MODE_SIZE (GET_MODE (x))) { default: gcc_unreachable (); case 8: offset = GEN_INT (INTVAL (offset) >> 3); break; case 4: offset = GEN_INT (INTVAL (offset) >> 2); break; case 2: offset = GEN_INT (INTVAL (offset) >> 1); break; case 1: break; } output_address (offset); } break; case CONST_DOUBLE : /* We handle SFmode constants here as output_addr_const doesn't. */ if (GET_MODE (x) == SFmode) { REAL_VALUE_TYPE d; long l; REAL_VALUE_FROM_CONST_DOUBLE (d, x); REAL_VALUE_TO_TARGET_SINGLE (d, l); fprintf (file, "%s0x%08lx", IMMEDIATE_PREFIX, l); break; } /* Fall through. Let output_addr_const deal with it. */ case CONST_INT: fprintf(file,"%s",IMMEDIATE_PREFIX); if (code == 'C' || code == 'X') { fprintf (file, "%ld", (long) (INTVAL (x) / current_frame_info.stld_sz)); break; } /* Fall through */ default : output_addr_const (file, x); break; } } /* Print a memory address as an operand to reference that memory location. */ static void epiphany_print_operand_address (FILE *file, rtx addr) { register rtx base, index = 0; int offset = 0; switch (GET_CODE (addr)) { case REG : fputs (reg_names[REGNO (addr)], file); break; case SYMBOL_REF : if (/*???*/ 0 && SYMBOL_REF_FUNCTION_P (addr)) { output_addr_const (file, addr); } else { output_addr_const (file, addr); } break; case PLUS : if (GET_CODE (XEXP (addr, 0)) == CONST_INT) offset = INTVAL (XEXP (addr, 0)), base = XEXP (addr, 1); else if (GET_CODE (XEXP (addr, 1)) == CONST_INT) offset = INTVAL (XEXP (addr, 1)), base = XEXP (addr, 0); else base = XEXP (addr, 0), index = XEXP (addr, 1); gcc_assert (GET_CODE (base) == REG); fputs (reg_names[REGNO (base)], file); if (index == 0) { /* ** ++rk quirky method to scale offset for ld/str....... */ fprintf (file, ",%s%d", IMMEDIATE_PREFIX, offset/current_frame_info.stld_sz); } else { switch (GET_CODE (index)) { case REG: fprintf (file, ",%s", reg_names[REGNO (index)]); break; case SYMBOL_REF: fputc (',', file), output_addr_const (file, index); break; default: gcc_unreachable (); } } break; case PRE_INC: case PRE_DEC: case POST_INC: case POST_DEC: case POST_MODIFY: /* We shouldn't get here as we've lost the mode of the memory object (which says how much to inc/dec by. */ gcc_unreachable (); break; default: output_addr_const (file, addr); break; } } void epiphany_final_prescan_insn (rtx insn ATTRIBUTE_UNUSED, rtx *opvec ATTRIBUTE_UNUSED, int noperands ATTRIBUTE_UNUSED) { int i = epiphany_n_nops; rtx pat ATTRIBUTE_UNUSED; while (i--) fputs ("\tnop\n", asm_out_file); } /* Worker function for TARGET_RETURN_IN_MEMORY. */ static bool epiphany_return_in_memory (const_tree type, const_tree fntype ATTRIBUTE_UNUSED) { HOST_WIDE_INT size = int_size_in_bytes (type); if (AGGREGATE_TYPE_P (type) && (TYPE_MODE (type) == BLKmode || TYPE_NEEDS_CONSTRUCTING (type))) return true; return (size == -1 || size > 8); } /* For EPIPHANY, All aggregates and arguments greater than 8 bytes are passed by reference. */ static bool epiphany_pass_by_reference (cumulative_args_t ca ATTRIBUTE_UNUSED, enum machine_mode mode, const_tree type, bool named ATTRIBUTE_UNUSED) { if (type) { if (AGGREGATE_TYPE_P (type) && (mode == BLKmode || TYPE_NEEDS_CONSTRUCTING (type))) return true; } return false; } static rtx epiphany_function_value (const_tree ret_type, const_tree fn_decl_or_type ATTRIBUTE_UNUSED, bool outgoing ATTRIBUTE_UNUSED) { enum machine_mode mode; mode = TYPE_MODE (ret_type); /* We must change the mode like PROMOTE_MODE does. ??? PROMOTE_MODE is ignored for non-scalar types. The set of types tested here has to be kept in sync with the one in explow.c:promote_mode. */ if (GET_MODE_CLASS (mode) == MODE_INT && GET_MODE_SIZE (mode) < 4 && (TREE_CODE (ret_type) == INTEGER_TYPE || TREE_CODE (ret_type) == ENUMERAL_TYPE || TREE_CODE (ret_type) == BOOLEAN_TYPE || TREE_CODE (ret_type) == OFFSET_TYPE)) mode = SImode; return gen_rtx_REG (mode, 0); } static rtx epiphany_libcall_value (enum machine_mode mode, const_rtx fun ATTRIBUTE_UNUSED) { return gen_rtx_REG (mode, 0); } bool epiphany_function_value_regno_p (const unsigned int regno ATTRIBUTE_UNUSED) { return regno == 0; } /* Fix up invalid option settings. */ static void epiphany_override_options (void) { if (epiphany_stack_offset < 4) error ("stack_offset must be at least 4"); if (epiphany_stack_offset & 3) error ("stack_offset must be a multiple of 4"); epiphany_stack_offset = (epiphany_stack_offset + 3) & -4; /* This needs to be done at start up. It's convenient to do it here. */ epiphany_init (); } /* For a DImode load / store SET, make a SImode set for a REG_FRAME_RELATED_EXPR note, using OFFSET to create a high or lowpart subreg. */ static rtx frame_subreg_note (rtx set, int offset) { rtx src = simplify_gen_subreg (SImode, SET_SRC (set), DImode, offset); rtx dst = simplify_gen_subreg (SImode, SET_DEST (set), DImode, offset); set = gen_rtx_SET (VOIDmode, dst ,src); RTX_FRAME_RELATED_P (set) = 1; return set; } static rtx frame_insn (rtx x) { int i; rtx note = NULL_RTX; if (GET_CODE (x) == PARALLEL) { rtx part = XVECEXP (x, 0, 0); if (GET_MODE (SET_DEST (part)) == DImode) { note = gen_rtx_PARALLEL (VOIDmode, rtvec_alloc (XVECLEN (x, 0) + 1)); XVECEXP (note, 0, 0) = frame_subreg_note (part, 0); XVECEXP (note, 0, 1) = frame_subreg_note (part, UNITS_PER_WORD); for (i = XVECLEN (x, 0) - 1; i >= 1; i--) { part = copy_rtx (XVECEXP (x, 0, i)); if (GET_CODE (part) == SET) RTX_FRAME_RELATED_P (part) = 1; XVECEXP (note, 0, i + 1) = part; } } else { for (i = XVECLEN (x, 0) - 1; i >= 0; i--) { part = XVECEXP (x, 0, i); if (GET_CODE (part) == SET) RTX_FRAME_RELATED_P (part) = 1; } } } else if (GET_CODE (x) == SET && GET_MODE (SET_DEST (x)) == DImode) note = gen_rtx_PARALLEL (VOIDmode, gen_rtvec (2, frame_subreg_note (x, 0), frame_subreg_note (x, UNITS_PER_WORD))); x = emit_insn (x); RTX_FRAME_RELATED_P (x) = 1; if (note) add_reg_note (x, REG_FRAME_RELATED_EXPR, note); return x; } static rtx frame_move_insn (rtx to, rtx from) { return frame_insn (gen_rtx_SET (VOIDmode, to, from)); } /* Generate a MEM referring to a varargs argument slot. */ static rtx gen_varargs_mem (enum machine_mode mode, rtx addr) { rtx mem = gen_rtx_MEM (mode, addr); MEM_NOTRAP_P (mem) = 1; set_mem_alias_set (mem, get_varargs_alias_set ()); return mem; } /* Emit instructions to save or restore registers in the range [MIN..LIMIT) . If EPILOGUE_P is 0, save; if it is one, restore. ADDR is the stack slot to save the first register to; subsequent registers are written to lower addresses. However, the order of register pairs can be reversed in order to use double-word load-store instructions. Likewise, an unpaired single word save slot can be skipped while double saves are carried out, and reused when a single register is to be saved. */ static void epiphany_emit_save_restore (int min, int limit, rtx addr, int epilogue_p) { int i; int stack_offset = current_frame_info.first_slot >= 0 ? epiphany_stack_offset : 0; rtx skipped_mem = NULL_RTX; int last_saved = limit - 1; if (!optimize) while (last_saved >= 0 && !TEST_HARD_REG_BIT (current_frame_info.gmask, last_saved)) last_saved--; for (i = 0; i < limit; i++) { enum machine_mode mode = word_mode; rtx mem, reg; int n = i; rtx (*gen_mem) (enum machine_mode, rtx) = gen_frame_mem; /* Make sure we push the arguments in the right order. */ if (n < MAX_EPIPHANY_PARM_REGS && crtl->args.pretend_args_size) { n = MAX_EPIPHANY_PARM_REGS - 1 - n; gen_mem = gen_varargs_mem; } if (stack_offset == current_frame_info.first_slot_size && current_frame_info.first_slot >= 0) { if (current_frame_info.first_slot_size > UNITS_PER_WORD) { mode = DImode; addr = plus_constant (addr, - (HOST_WIDE_INT) UNITS_PER_WORD); } if (i-- < min || !epilogue_p) goto next_slot; n = current_frame_info.first_slot; gen_mem = gen_frame_mem; } else if (n == UNKNOWN_REGNUM && stack_offset > current_frame_info.first_slot_size) { i--; goto next_slot; } else if (!TEST_HARD_REG_BIT (current_frame_info.gmask, n)) continue; else if (i < min) goto next_slot; /* Check for a register pair to save. */ if (n == i && (n >= MAX_EPIPHANY_PARM_REGS || crtl->args.pretend_args_size == 0) && (n & 1) == 0 && n+1 < limit && TEST_HARD_REG_BIT (current_frame_info.gmask, n+1)) { /* If it fits in the current stack slot pair, place it there. */ if (GET_CODE (addr) == PLUS && (stack_offset & 7) == 0 && stack_offset != 2 * UNITS_PER_WORD && (current_frame_info.last_slot < 0 || INTVAL (XEXP (addr, 1)) != UNITS_PER_WORD) && (n+1 != last_saved || !skipped_mem)) { mode = DImode; i++; addr = plus_constant (addr, - (HOST_WIDE_INT) UNITS_PER_WORD); } /* If it fits in the following stack slot pair, that's fine, too. */ else if (GET_CODE (addr) == PLUS && (stack_offset & 7) == 4 && stack_offset != 2 * UNITS_PER_WORD && stack_offset != 3 * UNITS_PER_WORD && (current_frame_info.last_slot < 0 || INTVAL (XEXP (addr, 1)) != 2 * UNITS_PER_WORD) && n + 1 != last_saved) { gcc_assert (!skipped_mem); stack_offset -= GET_MODE_SIZE (mode); skipped_mem = gen_mem (mode, addr); mode = DImode; i++; addr = plus_constant (addr, - (HOST_WIDE_INT) 2 * UNITS_PER_WORD); } } reg = gen_rtx_REG (mode, n); if (mode != DImode && skipped_mem) mem = skipped_mem; else mem = gen_mem (mode, addr); if (!epilogue_p) frame_move_insn (mem, reg); else if (n >= MAX_EPIPHANY_PARM_REGS || !crtl->args.pretend_args_size) emit_move_insn (reg, mem); if (mem == skipped_mem) { skipped_mem = NULL_RTX; continue; } next_slot: addr = plus_constant (addr, - (HOST_WIDE_INT) UNITS_PER_WORD); stack_offset -= GET_MODE_SIZE (mode); } } void epiphany_expand_prologue (void) { int interrupt_p; enum epiphany_function_type fn_type; rtx addr, mem, off, reg; rtx save_config; if (!current_frame_info.initialized) epiphany_compute_frame_size (get_frame_size ()); /* It is debatable if we should adjust this by epiphany_stack_offset. */ if (flag_stack_usage_info) current_function_static_stack_size = current_frame_info.total_size; fn_type = epiphany_compute_function_type (current_function_decl); interrupt_p = EPIPHANY_INTERRUPT_P (fn_type); if (interrupt_p) { addr = plus_constant (stack_pointer_rtx, - (HOST_WIDE_INT) 2 * UNITS_PER_WORD); if (!lookup_attribute ("forwarder_section", DECL_ATTRIBUTES (current_function_decl)) || !epiphany_is_long_call_p (XEXP (DECL_RTL (current_function_decl), 0))) frame_move_insn (gen_frame_mem (DImode, addr), gen_rtx_REG (DImode, GPR_0)); frame_move_insn (gen_rtx_REG (SImode, GPR_0), gen_rtx_REG (word_mode, STATUS_REGNUM)); frame_move_insn (gen_rtx_REG (SImode, GPR_0+1), gen_rtx_REG (word_mode, IRET_REGNUM)); mem = gen_frame_mem (BLKmode, stack_pointer_rtx); off = GEN_INT (-current_frame_info.first_slot_offset); frame_insn (gen_stack_adjust_add (off, mem)); if (!epiphany_uninterruptible_p (current_function_decl)) emit_insn (gen_gie ()); addr = plus_constant (stack_pointer_rtx, current_frame_info.first_slot_offset - (HOST_WIDE_INT) 3 * UNITS_PER_WORD); } else { addr = plus_constant (stack_pointer_rtx, epiphany_stack_offset - (HOST_WIDE_INT) UNITS_PER_WORD); epiphany_emit_save_restore (0, current_frame_info.small_threshold, addr, 0); /* Allocate register save area; for small to medium size frames, allocate the entire frame; this is joint with one register save. */ if (current_frame_info.first_slot >= 0) { enum machine_mode mode = (current_frame_info.first_slot_size == UNITS_PER_WORD ? word_mode : DImode); off = GEN_INT (-current_frame_info.first_slot_offset); mem = gen_frame_mem (BLKmode, gen_rtx_PLUS (Pmode, stack_pointer_rtx, off)); frame_insn (gen_stack_adjust_str (gen_frame_mem (mode, stack_pointer_rtx), gen_rtx_REG (mode, current_frame_info.first_slot), off, mem)); addr = plus_constant (addr, current_frame_info.first_slot_offset); } } epiphany_emit_save_restore (current_frame_info.small_threshold, FIRST_PSEUDO_REGISTER, addr, 0); if (current_frame_info.need_fp) frame_move_insn (hard_frame_pointer_rtx, stack_pointer_rtx); /* For large frames, allocate bulk of frame. This is usually joint with one register save. */ if (current_frame_info.last_slot >= 0) { gcc_assert (current_frame_info.last_slot != GPR_FP || (!current_frame_info.need_fp && current_frame_info.first_slot < 0)); off = GEN_INT (-current_frame_info.last_slot_offset); mem = gen_frame_mem (BLKmode, gen_rtx_PLUS (Pmode, stack_pointer_rtx, off)); reg = gen_rtx_REG (Pmode, GPR_IP); frame_move_insn (reg, off); frame_insn (gen_stack_adjust_str (gen_frame_mem (word_mode, stack_pointer_rtx), gen_rtx_REG (word_mode, current_frame_info.last_slot), reg, mem)); } /* If there is only one or no register to save, yet we have a large frame, use an add. */ else if (current_frame_info.last_slot_offset) { mem = gen_frame_mem (BLKmode, plus_constant (stack_pointer_rtx, current_frame_info.last_slot_offset)); off = GEN_INT (-current_frame_info.last_slot_offset); if (!SIMM11 (INTVAL (off))) { reg = gen_rtx_REG (Pmode, GPR_IP); frame_move_insn (reg, off); off = reg; } frame_insn (gen_stack_adjust_add (off, mem)); } /* Mode switching uses get_hard_reg_initial_val after emit_initial_value_sets, so we have to fix this up now. */ save_config = has_hard_reg_initial_val (SImode, CONFIG_REGNUM); if (save_config) { if (REG_P (save_config)) { if (REGNO (save_config) >= FIRST_PSEUDO_REGISTER) gcc_assert (!df_regs_ever_live_p (REGNO (save_config))); else frame_move_insn (save_config, get_hard_reg_initial_reg (save_config)); } else { rtx save_dst = save_config; reg = gen_rtx_REG (SImode, GPR_IP); gcc_assert (MEM_P (save_dst)); if (!memory_operand (save_dst, SImode)) { rtx addr = XEXP (save_dst, 0); rtx reg2 = gen_rtx_REG (SImode, GPR_16); gcc_assert (GET_CODE (addr) == PLUS); gcc_assert (XEXP (addr, 0) == hard_frame_pointer_rtx || XEXP (addr, 0) == stack_pointer_rtx); emit_move_insn (reg2, XEXP (addr, 1)); save_dst = replace_equiv_address (save_dst, gen_rtx_PLUS (Pmode, XEXP (addr, 0), reg2)); } emit_move_insn (reg, get_hard_reg_initial_reg (save_config)); emit_move_insn (save_dst, reg); } } } void epiphany_expand_epilogue (int sibcall_p) { int interrupt_p; enum epiphany_function_type fn_type; rtx mem, addr, reg, off; HOST_WIDE_INT restore_offset; fn_type = epiphany_compute_function_type( current_function_decl); interrupt_p = EPIPHANY_INTERRUPT_P (fn_type); /* For variable frames, deallocate bulk of frame. */ if (current_frame_info.need_fp) { mem = gen_frame_mem (BLKmode, stack_pointer_rtx); emit_insn (gen_stack_adjust_mov (mem)); } /* Else for large static frames, deallocate bulk of frame. */ else if (current_frame_info.last_slot_offset) { mem = gen_frame_mem (BLKmode, stack_pointer_rtx); reg = gen_rtx_REG (Pmode, GPR_IP); emit_move_insn (reg, GEN_INT (current_frame_info.last_slot_offset)); emit_insn (gen_stack_adjust_add (reg, mem)); } restore_offset = (interrupt_p ? - 3 * UNITS_PER_WORD : epiphany_stack_offset - (HOST_WIDE_INT) UNITS_PER_WORD); addr = plus_constant (stack_pointer_rtx, (current_frame_info.first_slot_offset + restore_offset)); epiphany_emit_save_restore (current_frame_info.small_threshold, FIRST_PSEUDO_REGISTER, addr, 1); if (interrupt_p && !epiphany_uninterruptible_p (current_function_decl)) emit_insn (gen_gid ()); off = GEN_INT (current_frame_info.first_slot_offset); mem = gen_frame_mem (BLKmode, stack_pointer_rtx); /* For large / variable size frames, deallocating the register save area is joint with one register restore; for medium size frames, we use a dummy post-increment load to dealloacte the whole frame. */ if (!SIMM11 (INTVAL (off)) || current_frame_info.last_slot >= 0) { emit_insn (gen_stack_adjust_ldr (gen_rtx_REG (word_mode, (current_frame_info.last_slot >= 0 ? current_frame_info.last_slot : GPR_IP)), gen_frame_mem (word_mode, stack_pointer_rtx), off, mem)); } /* While for small frames, we deallocate the entire frame with one add. */ else if (INTVAL (off)) { emit_insn (gen_stack_adjust_add (off, mem)); } if (interrupt_p) { emit_move_insn (gen_rtx_REG (word_mode, STATUS_REGNUM), gen_rtx_REG (SImode, GPR_0)); emit_move_insn (gen_rtx_REG (word_mode, IRET_REGNUM), gen_rtx_REG (SImode, GPR_0+1)); addr = plus_constant (stack_pointer_rtx, - (HOST_WIDE_INT) 2 * UNITS_PER_WORD); emit_move_insn (gen_rtx_REG (DImode, GPR_0), gen_frame_mem (DImode, addr)); } addr = plus_constant (stack_pointer_rtx, epiphany_stack_offset - (HOST_WIDE_INT) UNITS_PER_WORD); epiphany_emit_save_restore (0, current_frame_info.small_threshold, addr, 1); if (!sibcall_p) { if (interrupt_p) emit_jump_insn (gen_return_internal_interrupt()); else emit_jump_insn (gen_return_i ()); } } int epiphany_initial_elimination_offset (int from, int to) { epiphany_compute_frame_size (get_frame_size ()); if (from == FRAME_POINTER_REGNUM && to == STACK_POINTER_REGNUM) return current_frame_info.total_size - current_frame_info.reg_size; if (from == FRAME_POINTER_REGNUM && to == HARD_FRAME_POINTER_REGNUM) return current_frame_info.first_slot_offset - current_frame_info.reg_size; if (from == ARG_POINTER_REGNUM && to == STACK_POINTER_REGNUM) return (current_frame_info.total_size - ((current_frame_info.pretend_size + 4) & -8)); if (from == ARG_POINTER_REGNUM && to == HARD_FRAME_POINTER_REGNUM) return (current_frame_info.first_slot_offset - ((current_frame_info.pretend_size + 4) & -8)); gcc_unreachable (); } static int epiphany_issue_rate (void) { return 2; } /* Function to update the integer COST based on the relationship between INSN that is dependent on DEP_INSN through the dependence LINK. The default is to make no adjustment to COST. This can be used for example to specify to the scheduler that an output- or anti-dependence does not incur the same cost as a data-dependence. The return value should be the new value for COST. */ static int epiphany_adjust_cost (rtx insn, rtx link, rtx dep_insn, int cost) { if (REG_NOTE_KIND (link) == 0) { rtx dep_set; if (recog_memoized (insn) < 0 || recog_memoized (dep_insn) < 0) return cost; dep_set = single_set (dep_insn); /* The latency that we specify in the scheduling description refers to the actual output, not to an auto-increment register; for that, the latency is one. */ if (dep_set && MEM_P (SET_SRC (dep_set)) && cost > 1) { rtx set = single_set (insn); if (set && !reg_mentioned_p (SET_DEST (dep_set), SET_SRC (set)) && (!MEM_P (SET_DEST (set)) || !reg_mentioned_p (SET_DEST (dep_set), XEXP (SET_DEST (set), 0)))) cost = 1; } } return cost; } #define REG_OK_FOR_INDEX_P(X) REG_OK_FOR_BASE_P (X) #define RTX_OK_FOR_BASE_P(X) \ (REG_P (X) && REG_OK_FOR_BASE_P (X)) #define RTX_OK_FOR_INDEX_P(MODE, X) \ ((GET_MODE_CLASS (MODE) != MODE_VECTOR_INT \ || epiphany_vect_align >= GET_MODE_SIZE (MODE)) \ && (REG_P (X) && REG_OK_FOR_INDEX_P (X))) #define LEGITIMATE_OFFSET_ADDRESS_P(MODE, X) \ (GET_CODE (X) == PLUS \ && RTX_OK_FOR_BASE_P (XEXP (X, 0)) \ && (RTX_OK_FOR_INDEX_P (MODE, XEXP (X, 1)) \ || RTX_OK_FOR_OFFSET_P (MODE, XEXP (X, 1)))) static bool epiphany_legitimate_address_p (enum machine_mode mode, rtx x, bool strict) { #define REG_OK_FOR_BASE_P(X) \ (strict ? GPR_P (REGNO (X)) : GPR_AP_OR_PSEUDO_P (REGNO (X))) if (RTX_OK_FOR_BASE_P (x)) return true; if (RTX_FRAME_OFFSET_P (x)) return true; if (LEGITIMATE_OFFSET_ADDRESS_P (mode, x)) return true; if (TARGET_POST_INC && (GET_CODE (x) == POST_DEC || GET_CODE (x) == POST_INC) && RTX_OK_FOR_BASE_P (XEXP ((x), 0))) return true; if ((TARGET_POST_MODIFY || reload_completed) && GET_CODE (x) == POST_MODIFY && GET_CODE (XEXP ((x), 1)) == PLUS && rtx_equal_p (XEXP ((x), 0), XEXP (XEXP ((x), 1), 0)) && LEGITIMATE_OFFSET_ADDRESS_P (mode, XEXP ((x), 1))) return true; if (mode == BLKmode) return true; return false; } static reg_class_t epiphany_secondary_reload (bool in_p, rtx x, reg_class_t rclass, enum machine_mode mode ATTRIBUTE_UNUSED, secondary_reload_info *sri) { /* This could give more reload inheritance, but we are missing some reload infrastructure. */ if (0) if (in_p && GET_CODE (x) == UNSPEC && satisfies_constraint_Sra (x) && !satisfies_constraint_Rra (x)) { gcc_assert (rclass == GENERAL_REGS); sri->icode = CODE_FOR_reload_insi_ra; return NO_REGS; } return NO_REGS; } bool epiphany_is_long_call_p (rtx x) { tree decl = SYMBOL_REF_DECL (x); bool ret_val = !TARGET_SHORT_CALLS; tree attrs; /* ??? Is it safe to default to ret_val if decl is NULL? We should probably encode information via encode_section_info, and also have (an) option(s) to take SYMBOL_FLAG_LOCAL and/or SYMBOL_FLAG_EXTERNAL into account. */ if (decl) { attrs = TYPE_ATTRIBUTES (TREE_TYPE (decl)); if (lookup_attribute ("long_call", attrs)) ret_val = true; else if (lookup_attribute ("short_call", attrs)) ret_val = false; } return ret_val; } bool epiphany_small16 (rtx x) { rtx base = x; rtx offs ATTRIBUTE_UNUSED = const0_rtx; if (GET_CODE (x) == CONST && GET_CODE (XEXP (x, 0)) == PLUS) { base = XEXP (XEXP (x, 0), 0); offs = XEXP (XEXP (x, 0), 1); } if (GET_CODE (base) == SYMBOL_REF && SYMBOL_REF_FUNCTION_P (base) && epiphany_is_long_call_p (base)) return false; return TARGET_SMALL16 != 0; } /* Return nonzero if it is ok to make a tail-call to DECL. */ static bool epiphany_function_ok_for_sibcall (tree decl, tree exp) { bool cfun_interrupt_p, call_interrupt_p; cfun_interrupt_p = EPIPHANY_INTERRUPT_P (epiphany_compute_function_type (current_function_decl)); if (decl) call_interrupt_p = EPIPHANY_INTERRUPT_P (epiphany_compute_function_type (decl)); else { tree fn_type = TREE_TYPE (CALL_EXPR_FN (exp)); gcc_assert (POINTER_TYPE_P (fn_type)); fn_type = TREE_TYPE (fn_type); gcc_assert (TREE_CODE (fn_type) == FUNCTION_TYPE || TREE_CODE (fn_type) == METHOD_TYPE); call_interrupt_p = lookup_attribute ("interrupt", TYPE_ATTRIBUTES (fn_type)) != NULL; } /* Don't tailcall from or to an ISR routine - although we could in principle tailcall from one ISR routine to another, we'd need to handle this in sibcall_epilogue to make it work. */ if (cfun_interrupt_p || call_interrupt_p) return false; /* Everything else is ok. */ return true; } /* T is a function declaration or the MEM_EXPR of a MEM passed to a call expander. Return true iff the type of T has the uninterruptible attribute. If T is NULL, return false. */ bool epiphany_uninterruptible_p (tree t) { tree attrs; if (t) { attrs = TYPE_ATTRIBUTES (TREE_TYPE (t)); if (lookup_attribute ("disinterrupt", attrs)) return true; } return false; } bool epiphany_call_uninterruptible_p (rtx mem) { rtx addr = XEXP (mem, 0); tree t = NULL_TREE; if (GET_CODE (addr) == SYMBOL_REF) t = SYMBOL_REF_DECL (addr); if (!t) t = MEM_EXPR (mem); return epiphany_uninterruptible_p (t); } static enum machine_mode epiphany_promote_function_mode (const_tree type, enum machine_mode mode, int *punsignedp ATTRIBUTE_UNUSED, const_tree funtype ATTRIBUTE_UNUSED, int for_return ATTRIBUTE_UNUSED) { int dummy; return promote_mode (type, mode, &dummy); } static void epiphany_conditional_register_usage (void) { int i; if (PIC_OFFSET_TABLE_REGNUM != INVALID_REGNUM) { fixed_regs[PIC_OFFSET_TABLE_REGNUM] = 1; call_used_regs[PIC_OFFSET_TABLE_REGNUM] = 1; } if (TARGET_HALF_REG_FILE) { for (i = 32; i <= 63; i++) { fixed_regs[i] = 1; call_used_regs[i] = 1; } } if (epiphany_m1reg >= 0) { fixed_regs[epiphany_m1reg] = 1; call_used_regs[epiphany_m1reg] = 1; } if (!TARGET_PREFER_SHORT_INSN_REGS) CLEAR_HARD_REG_SET (reg_class_contents[SHORT_INSN_REGS]); COPY_HARD_REG_SET (reg_class_contents[SIBCALL_REGS], reg_class_contents[GENERAL_REGS]); /* It would be simpler and quicker if we could just use AND_COMPL_HARD_REG_SET, alas, call_used_reg_set is yet uninitialized; it is set up later by our caller. */ for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) if (!call_used_regs[i]) CLEAR_HARD_REG_BIT (reg_class_contents[SIBCALL_REGS], i); } /* Determine where to put an argument 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). */ /* On the EPIPHANY the first MAX_EPIPHANY_PARM_REGS args are normally in registers and the rest are pushed. */ static rtx epiphany_function_arg (cumulative_args_t cum_v, enum machine_mode mode, const_tree type, bool named ATTRIBUTE_UNUSED) { CUMULATIVE_ARGS cum = *get_cumulative_args (cum_v); if (PASS_IN_REG_P (cum, mode, type)) return gen_rtx_REG (mode, ROUND_ADVANCE_CUM (cum, mode, type)); return 0; } /* 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 epiphany_function_arg_advance (cumulative_args_t cum_v, enum machine_mode mode, const_tree type, bool named ATTRIBUTE_UNUSED) { CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v); *cum = ROUND_ADVANCE_CUM (*cum, mode, type) + ROUND_ADVANCE_ARG (mode, type); } /* Nested function support. An epiphany trampoline looks like this: mov r16,%low(fnaddr) movt r16,%high(fnaddr) mov ip,%low(cxt) movt ip,%high(cxt) jr r16 */ #define EPIPHANY_LOW_RTX(X) \ (gen_rtx_IOR (SImode, \ gen_rtx_ASHIFT (SImode, \ gen_rtx_AND (SImode, (X), GEN_INT (0xff)), GEN_INT (5)), \ gen_rtx_ASHIFT (SImode, \ gen_rtx_AND (SImode, (X), GEN_INT (0xff00)), GEN_INT (12)))) #define EPIPHANY_HIGH_RTX(X) \ EPIPHANY_LOW_RTX (gen_rtx_LSHIFTRT (SImode, (X), GEN_INT (16))) /* Emit RTL insns to initialize the variable parts of a trampoline. 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. */ static void epiphany_trampoline_init (rtx tramp_mem, tree fndecl, rtx cxt) { rtx fnaddr = XEXP (DECL_RTL (fndecl), 0); rtx tramp = force_reg (Pmode, XEXP (tramp_mem, 0)); emit_move_insn (gen_rtx_MEM (SImode, plus_constant (tramp, 0)), gen_rtx_IOR (SImode, GEN_INT (0x4002000b), EPIPHANY_LOW_RTX (fnaddr))); emit_move_insn (gen_rtx_MEM (SImode, plus_constant (tramp, 4)), gen_rtx_IOR (SImode, GEN_INT (0x5002000b), EPIPHANY_HIGH_RTX (fnaddr))); emit_move_insn (gen_rtx_MEM (SImode, plus_constant (tramp, 8)), gen_rtx_IOR (SImode, GEN_INT (0x2002800b), EPIPHANY_LOW_RTX (cxt))); emit_move_insn (gen_rtx_MEM (SImode, plus_constant (tramp, 12)), gen_rtx_IOR (SImode, GEN_INT (0x3002800b), EPIPHANY_HIGH_RTX (cxt))); emit_move_insn (gen_rtx_MEM (SImode, plus_constant (tramp, 16)), GEN_INT (0x0802014f)); } bool epiphany_optimize_mode_switching (int entity) { if (MACHINE_FUNCTION (cfun)->sw_entities_processed & (1 << entity)) return false; switch (entity) { case EPIPHANY_MSW_ENTITY_AND: case EPIPHANY_MSW_ENTITY_OR: return true; case EPIPHANY_MSW_ENTITY_NEAREST: case EPIPHANY_MSW_ENTITY_TRUNC: return optimize > 0; case EPIPHANY_MSW_ENTITY_ROUND_UNKNOWN: return MACHINE_FUNCTION (cfun)->unknown_mode_uses != 0; case EPIPHANY_MSW_ENTITY_ROUND_KNOWN: return (MACHINE_FUNCTION (cfun)->sw_entities_processed & (1 << EPIPHANY_MSW_ENTITY_ROUND_UNKNOWN)) != 0; case EPIPHANY_MSW_ENTITY_FPU_OMNIBUS: return optimize == 0 || current_pass == &pass_mode_switch_use.pass; } gcc_unreachable (); } int epiphany_mode_priority_to_mode (int entity, unsigned priority) { if (entity == EPIPHANY_MSW_ENTITY_AND || entity == EPIPHANY_MSW_ENTITY_OR) return priority; if (priority > 3) switch (priority) { case 4: return FP_MODE_ROUND_UNKNOWN; case 5: return FP_MODE_NONE; default: gcc_unreachable (); } switch ((enum attr_fp_mode) epiphany_normal_fp_mode) { case FP_MODE_INT: switch (priority) { case 0: return FP_MODE_INT; case 1: return epiphany_normal_fp_rounding; case 2: return (epiphany_normal_fp_rounding == FP_MODE_ROUND_NEAREST ? FP_MODE_ROUND_TRUNC : FP_MODE_ROUND_NEAREST); case 3: return FP_MODE_CALLER; } case FP_MODE_ROUND_NEAREST: case FP_MODE_CALLER: switch (priority) { case 0: return FP_MODE_ROUND_NEAREST; case 1: return FP_MODE_ROUND_TRUNC; case 2: return FP_MODE_INT; case 3: return FP_MODE_CALLER; } case FP_MODE_ROUND_TRUNC: switch (priority) { case 0: return FP_MODE_ROUND_TRUNC; case 1: return FP_MODE_ROUND_NEAREST; case 2: return FP_MODE_INT; case 3: return FP_MODE_CALLER; } case FP_MODE_ROUND_UNKNOWN: case FP_MODE_NONE: gcc_unreachable (); } gcc_unreachable (); } int epiphany_mode_needed (int entity, rtx insn) { enum attr_fp_mode mode; if (recog_memoized (insn) < 0) { if (entity == EPIPHANY_MSW_ENTITY_AND || entity == EPIPHANY_MSW_ENTITY_OR) return 2; return FP_MODE_NONE; } mode = get_attr_fp_mode (insn); switch (entity) { case EPIPHANY_MSW_ENTITY_AND: return mode != FP_MODE_INT ? 1 : 2; case EPIPHANY_MSW_ENTITY_OR: return mode == FP_MODE_INT ? 1 : 2; case EPIPHANY_MSW_ENTITY_ROUND_KNOWN: if (recog_memoized (insn) == CODE_FOR_set_fp_mode) mode = (enum attr_fp_mode) epiphany_mode_after (entity, mode, insn); /* Fall through. */ case EPIPHANY_MSW_ENTITY_NEAREST: case EPIPHANY_MSW_ENTITY_TRUNC: if (mode == FP_MODE_ROUND_UNKNOWN) { MACHINE_FUNCTION (cfun)->unknown_mode_uses++; return FP_MODE_NONE; } return mode; case EPIPHANY_MSW_ENTITY_ROUND_UNKNOWN: if (mode == FP_MODE_ROUND_NEAREST || mode == FP_MODE_ROUND_TRUNC) return FP_MODE_ROUND_UNKNOWN; return mode; case EPIPHANY_MSW_ENTITY_FPU_OMNIBUS: if (mode == FP_MODE_ROUND_UNKNOWN) return epiphany_normal_fp_rounding; return mode; default: gcc_unreachable (); } } int epiphany_mode_entry_exit (int entity, bool exit) { int normal_mode = epiphany_normal_fp_mode ; MACHINE_FUNCTION (cfun)->sw_entities_processed |= (1 << entity); if (epiphany_is_interrupt_p (current_function_decl)) normal_mode = FP_MODE_CALLER; switch (entity) { case EPIPHANY_MSW_ENTITY_AND: if (exit) return normal_mode != FP_MODE_INT ? 1 : 2; return 0; case EPIPHANY_MSW_ENTITY_OR: if (exit) return normal_mode == FP_MODE_INT ? 1 : 2; return 0; case EPIPHANY_MSW_ENTITY_ROUND_UNKNOWN: if (normal_mode == FP_MODE_ROUND_NEAREST || normal_mode == FP_MODE_ROUND_TRUNC) return FP_MODE_ROUND_UNKNOWN; /* Fall through. */ case EPIPHANY_MSW_ENTITY_NEAREST: case EPIPHANY_MSW_ENTITY_TRUNC: case EPIPHANY_MSW_ENTITY_ROUND_KNOWN: case EPIPHANY_MSW_ENTITY_FPU_OMNIBUS: return normal_mode; default: gcc_unreachable (); } } int epiphany_mode_after (int entity, int last_mode, rtx insn) { /* We have too few call-saved registers to hope to keep the masks across calls. */ if (entity == EPIPHANY_MSW_ENTITY_AND || entity == EPIPHANY_MSW_ENTITY_OR) { if (GET_CODE (insn) == CALL_INSN) return 0; return last_mode; } if (recog_memoized (insn) < 0) return last_mode; if (get_attr_fp_mode (insn) == FP_MODE_ROUND_UNKNOWN && last_mode != FP_MODE_ROUND_NEAREST && last_mode != FP_MODE_ROUND_TRUNC) { if (entity == EPIPHANY_MSW_ENTITY_NEAREST) return FP_MODE_ROUND_NEAREST; if (entity == EPIPHANY_MSW_ENTITY_TRUNC) return FP_MODE_ROUND_TRUNC; } if (recog_memoized (insn) == CODE_FOR_set_fp_mode) { rtx src = SET_SRC (XVECEXP (PATTERN (insn), 0, 0)); int fp_mode; if (REG_P (src)) return FP_MODE_CALLER; fp_mode = INTVAL (XVECEXP (XEXP (src, 0), 0, 0)); if (entity == EPIPHANY_MSW_ENTITY_ROUND_UNKNOWN && (fp_mode == FP_MODE_ROUND_NEAREST || fp_mode == EPIPHANY_MSW_ENTITY_TRUNC)) return FP_MODE_ROUND_UNKNOWN; return fp_mode; } return last_mode; } void emit_set_fp_mode (int entity, int mode, HARD_REG_SET regs_live ATTRIBUTE_UNUSED) { rtx save_cc, cc_reg, mask, src, src2; enum attr_fp_mode fp_mode; if (!MACHINE_FUNCTION (cfun)->and_mask) { MACHINE_FUNCTION (cfun)->and_mask = gen_reg_rtx (SImode); MACHINE_FUNCTION (cfun)->or_mask = gen_reg_rtx (SImode); } if (entity == EPIPHANY_MSW_ENTITY_AND) { gcc_assert (mode >= 0 && mode <= 2); if (mode == 1) emit_move_insn (MACHINE_FUNCTION (cfun)->and_mask, gen_int_mode (0xfff1fffe, SImode)); return; } else if (entity == EPIPHANY_MSW_ENTITY_OR) { gcc_assert (mode >= 0 && mode <= 2); if (mode == 1) emit_move_insn (MACHINE_FUNCTION (cfun)->or_mask, GEN_INT(0x00080000)); return; } fp_mode = (enum attr_fp_mode) mode; src = NULL_RTX; switch (fp_mode) { case FP_MODE_CALLER: src = get_hard_reg_initial_val (SImode, CONFIG_REGNUM); mask = MACHINE_FUNCTION (cfun)->and_mask; break; case FP_MODE_ROUND_UNKNOWN: MACHINE_FUNCTION (cfun)->unknown_mode_sets++; mask = MACHINE_FUNCTION (cfun)->and_mask; break; case FP_MODE_ROUND_NEAREST: if (entity == EPIPHANY_MSW_ENTITY_TRUNC) return; mask = MACHINE_FUNCTION (cfun)->and_mask; break; case FP_MODE_ROUND_TRUNC: if (entity == EPIPHANY_MSW_ENTITY_NEAREST) return; mask = MACHINE_FUNCTION (cfun)->and_mask; break; case FP_MODE_INT: mask = MACHINE_FUNCTION (cfun)->or_mask; break; case FP_MODE_NONE: default: gcc_unreachable (); } save_cc = gen_reg_rtx (CCmode); cc_reg = gen_rtx_REG (CCmode, CC_REGNUM); emit_move_insn (save_cc, cc_reg); mask = force_reg (SImode, mask); if (!src) { rtvec v = gen_rtvec (1, GEN_INT (fp_mode)); src = gen_rtx_CONST (SImode, gen_rtx_UNSPEC (SImode, v, UNSPEC_FP_MODE)); } if (entity == EPIPHANY_MSW_ENTITY_ROUND_KNOWN || entity == EPIPHANY_MSW_ENTITY_FPU_OMNIBUS) src2 = copy_rtx (src); else { rtvec v = gen_rtvec (1, GEN_INT (FP_MODE_ROUND_UNKNOWN)); src2 = gen_rtx_CONST (SImode, gen_rtx_UNSPEC (SImode, v, UNSPEC_FP_MODE)); } emit_insn (gen_set_fp_mode (src, src2, mask)); emit_move_insn (cc_reg, save_cc); } void epiphany_expand_set_fp_mode (rtx *operands) { rtx ctrl = gen_rtx_REG (SImode, CONFIG_REGNUM); rtx src = operands[0]; rtx mask_reg = operands[2]; rtx scratch = operands[3]; enum attr_fp_mode fp_mode; gcc_assert (rtx_equal_p (src, operands[1]) /* Sometimes reload gets silly and reloads the same pseudo into different registers. */ || (REG_P (src) && REG_P (operands[1]))); if (!epiphany_uninterruptible_p (current_function_decl)) emit_insn (gen_gid ()); emit_move_insn (scratch, ctrl); if (GET_CODE (src) == REG) { /* FP_MODE_CALLER */ emit_insn (gen_xorsi3 (scratch, scratch, src)); emit_insn (gen_andsi3 (scratch, scratch, mask_reg)); emit_insn (gen_xorsi3 (scratch, scratch, src)); } else { gcc_assert (GET_CODE (src) == CONST); src = XEXP (src, 0); fp_mode = (enum attr_fp_mode) INTVAL (XVECEXP (src, 0, 0)); switch (fp_mode) { case FP_MODE_ROUND_NEAREST: emit_insn (gen_andsi3 (scratch, scratch, mask_reg)); break; case FP_MODE_ROUND_TRUNC: emit_insn (gen_andsi3 (scratch, scratch, mask_reg)); emit_insn (gen_add2_insn (scratch, const1_rtx)); break; case FP_MODE_INT: emit_insn (gen_iorsi3 (scratch, scratch, mask_reg)); break; case FP_MODE_CALLER: case FP_MODE_ROUND_UNKNOWN: case FP_MODE_NONE: gcc_unreachable (); } } emit_move_insn (ctrl, scratch); if (!epiphany_uninterruptible_p (current_function_decl)) emit_insn (gen_gie ()); } void epiphany_insert_mode_switch_use (rtx insn, int entity ATTRIBUTE_UNUSED, int mode ATTRIBUTE_UNUSED) { rtx pat = PATTERN (insn); rtvec v; int len, i; rtx near = gen_rtx_REG (SImode, FP_NEAREST_REGNUM); rtx trunc = gen_rtx_REG (SImode, FP_TRUNCATE_REGNUM); if (entity != EPIPHANY_MSW_ENTITY_FPU_OMNIBUS) return; switch ((enum attr_fp_mode) get_attr_fp_mode (insn)) { case FP_MODE_ROUND_NEAREST: near = gen_rtx_USE (VOIDmode, near); trunc = gen_rtx_CLOBBER (VOIDmode, trunc); break; case FP_MODE_ROUND_TRUNC: near = gen_rtx_CLOBBER (VOIDmode, near); trunc = gen_rtx_USE (VOIDmode, trunc); break; case FP_MODE_ROUND_UNKNOWN: near = gen_rtx_USE (VOIDmode, gen_rtx_REG (SImode, FP_ANYFP_REGNUM)); trunc = copy_rtx (near); /* Fall through. */ case FP_MODE_INT: case FP_MODE_CALLER: near = gen_rtx_USE (VOIDmode, near); trunc = gen_rtx_USE (VOIDmode, trunc); break; case FP_MODE_NONE: gcc_unreachable (); } gcc_assert (GET_CODE (pat) == PARALLEL); len = XVECLEN (pat, 0); v = rtvec_alloc (len + 2); for (i = 0; i < len; i++) RTVEC_ELT (v, i) = XVECEXP (pat, 0, i); RTVEC_ELT (v, len) = near; RTVEC_ELT (v, len + 1) = trunc; pat = gen_rtx_PARALLEL (VOIDmode, v); PATTERN (insn) = pat; MACHINE_FUNCTION (cfun)->control_use_inserted = true; } bool epiphany_epilogue_uses (int regno) { if (regno == GPR_LR) return true; if (reload_completed && epiphany_is_interrupt_p (current_function_decl)) { if (fixed_regs[regno] && regno != STATUS_REGNUM && regno != IRET_REGNUM && regno != FP_NEAREST_REGNUM && regno != FP_TRUNCATE_REGNUM) return false; return true; } if (regno == FP_NEAREST_REGNUM && epiphany_normal_fp_mode != FP_MODE_ROUND_TRUNC) return true; if (regno == FP_TRUNCATE_REGNUM && epiphany_normal_fp_mode != FP_MODE_ROUND_NEAREST) return true; return false; } static unsigned int epiphany_min_divisions_for_recip_mul (enum machine_mode mode) { if (flag_reciprocal_math && mode == SFmode) /* We'll expand into a multiply-by-reciprocal anyway, so we might a well do it already at the tree level and expose it to further optimizations. */ return 1; return default_min_divisions_for_recip_mul (mode); } static enum machine_mode epiphany_preferred_simd_mode (enum machine_mode mode ATTRIBUTE_UNUSED) { return TARGET_VECT_DOUBLE ? DImode : SImode; } static bool epiphany_vector_mode_supported_p (enum machine_mode mode) { if (mode == V2SFmode) return true; if (GET_MODE_CLASS (mode) == MODE_VECTOR_INT && (GET_MODE_SIZE (mode) == 4 || GET_MODE_SIZE (mode) == 8)) return true; return false; } static bool epiphany_vector_alignment_reachable (const_tree type, bool is_packed) { /* Vectors which aren't in packed structures will not be less aligned than the natural alignment of their element type, so this is safe. */ if (TYPE_ALIGN_UNIT (type) == 4) return !is_packed; return default_builtin_vector_alignment_reachable (type, is_packed); } static bool epiphany_support_vector_misalignment (enum machine_mode mode, const_tree type, int misalignment, bool is_packed) { if (GET_MODE_SIZE (mode) == 8 && misalignment % 4 == 0) return true; return default_builtin_support_vector_misalignment (mode, type, misalignment, is_packed); } /* STRUCTURE_SIZE_BOUNDARY seems a bit crude in how it enlarges small structs. Make structs double-word-aligned it they are a double word or (potentially) larger; failing that, do the same for a size of 32 bits. */ unsigned epiphany_special_round_type_align (tree type, unsigned computed, unsigned specified) { unsigned align = MAX (computed, specified); tree field; HOST_WIDE_INT total, max; unsigned try_align = FASTEST_ALIGNMENT; if (maximum_field_alignment && try_align > maximum_field_alignment) try_align = maximum_field_alignment; if (align >= try_align) return align; for (max = 0, field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field)) { tree offset, size; if (TREE_CODE (field) != FIELD_DECL || TREE_TYPE (field) == error_mark_node) continue; offset = bit_position (field); size = DECL_SIZE (field); if (!host_integerp (offset, 1) || !host_integerp (size, 1) || TREE_INT_CST_LOW (offset) >= try_align || TREE_INT_CST_LOW (size) >= try_align) return try_align; total = TREE_INT_CST_LOW (offset) + TREE_INT_CST_LOW (size); if (total > max) max = total; } if (max >= (HOST_WIDE_INT) try_align) align = try_align; else if (try_align > 32 && max >= 32) align = max > 32 ? 64 : 32; return align; } /* Upping the alignment of arrays in structs is not only a performance enhancement, it also helps preserve assumptions about how arrays-at-the-end-of-structs work, like for struct gcov_fn_info in libgcov.c . */ unsigned epiphany_adjust_field_align (tree field, unsigned computed) { if (computed == 32 && TREE_CODE (TREE_TYPE (field)) == ARRAY_TYPE) { tree elmsz = TYPE_SIZE (TREE_TYPE (TREE_TYPE (field))); if (!host_integerp (elmsz, 1) || tree_low_cst (elmsz, 1) >= 32) return 64; } return computed; } /* Output code to add DELTA to the first argument, and then jump to FUNCTION. Used for C++ multiple inheritance. */ static void epiphany_output_mi_thunk (FILE *file, tree thunk ATTRIBUTE_UNUSED, HOST_WIDE_INT delta, HOST_WIDE_INT vcall_offset, tree function) { int this_regno = aggregate_value_p (TREE_TYPE (TREE_TYPE (function)), function) ? 1 : 0; const char *this_name = reg_names[this_regno]; const char *fname; /* We use IP and R16 as a scratch registers. */ gcc_assert (call_used_regs [GPR_IP]); gcc_assert (call_used_regs [GPR_16]); /* Add DELTA. When possible use a plain add, otherwise load it into a register first. */ if (delta == 0) ; /* Done. */ else if (SIMM11 (delta)) asm_fprintf (file, "\tadd\t%s,%s,%d\n", this_name, this_name, (int) delta); else if (delta < 0 && delta >= -0xffff) { asm_fprintf (file, "\tmov\tip,%d\n", (int) -delta); asm_fprintf (file, "\tsub\t%s,%s,ip\n", this_name, this_name); } else { asm_fprintf (file, "\tmov\tip,%%low(%ld)\n", (long) delta); if (delta & ~0xffff) asm_fprintf (file, "\tmovt\tip,%%high(%ld)\n", (long) delta); asm_fprintf (file, "\tadd\t%s,%s,ip\n", this_name, this_name); } /* If needed, add *(*THIS + VCALL_OFFSET) to THIS. */ if (vcall_offset != 0) { /* ldr ip,[this] --> temp = *this ldr ip,[ip,vcall_offset] > temp = *(*this + vcall_offset) add this,this,ip --> this+ = *(*this + vcall_offset) */ asm_fprintf (file, "\tldr\tip, [%s]\n", this_name); if (vcall_offset < -0x7ff * 4 || vcall_offset > 0x7ff * 4 || (vcall_offset & 3) != 0) { asm_fprintf (file, "\tmov\tr16, %%low(%ld)\n", (long) vcall_offset); asm_fprintf (file, "\tmovt\tr16, %%high(%ld)\n", (long) vcall_offset); asm_fprintf (file, "\tldr\tip, [ip,r16]\n"); } else asm_fprintf (file, "\tldr\tip, [ip,%d]\n", (int) vcall_offset / 4); asm_fprintf (file, "\tadd\t%s, %s, ip\n", this_name, this_name); } fname = XSTR (XEXP (DECL_RTL (function), 0), 0); if (epiphany_is_long_call_p (XEXP (DECL_RTL (function), 0))) { fputs ("\tmov\tip,%low(", file); assemble_name (file, fname); fputs (")\n\tmovt\tip,%high(", file); assemble_name (file, fname); fputs (")\n\tjr ip\n", file); } else { fputs ("\tb\t", file); assemble_name (file, fname); fputc ('\n', file); } } void epiphany_start_function (FILE *file, const char *name, tree decl) { /* If the function doesn't fit into the on-chip memory, it will have a section attribute - or lack of it - that denotes it goes somewhere else. But the architecture spec says that an interrupt vector still has to point to on-chip memory. So we must place a jump there to get to the actual function implementation. The forwarder_section attribute specifies the section where this jump goes. This mechanism can also be useful to have a shortcall destination for a function that is actually placed much farther away. */ tree attrs, int_attr, int_names, int_name, forwarder_attr; attrs = DECL_ATTRIBUTES (decl); int_attr = lookup_attribute ("interrupt", attrs); if (int_attr) for (int_names = TREE_VALUE (int_attr); int_names; int_names = TREE_CHAIN (int_names)) { char buf[99]; int_name = TREE_VALUE (int_names); sprintf (buf, "ivt_entry_%.80s", TREE_STRING_POINTER (int_name)); switch_to_section (get_section (buf, SECTION_CODE, decl)); fputs ("\tb\t", file); assemble_name (file, name); fputc ('\n', file); } forwarder_attr = lookup_attribute ("forwarder_section", attrs); if (forwarder_attr) { const char *prefix = "__forwarder_dst_"; char *dst_name = (char *) alloca (strlen (prefix) + strlen (name) + 1); strcpy (dst_name, prefix); strcat (dst_name, name); forwarder_attr = TREE_VALUE (TREE_VALUE (forwarder_attr)); switch_to_section (get_section (TREE_STRING_POINTER (forwarder_attr), SECTION_CODE, decl)); ASM_OUTPUT_FUNCTION_LABEL (file, name, decl); if (epiphany_is_long_call_p (XEXP (DECL_RTL (decl), 0))) { int tmp = GPR_0; if (int_attr) fputs ("\tstrd r0,[sp,-1]\n", file); else tmp = GPR_16; gcc_assert (call_used_regs[tmp]); fprintf (file, "\tmov r%d,%%low(", tmp); assemble_name (file, dst_name); fprintf (file, ")\n" "\tmovt r%d,%%high(", tmp); assemble_name (file, dst_name); fprintf (file, ")\n" "\tjr r%d\n", tmp); } else { fputs ("\tb\t", file); assemble_name (file, dst_name); fputc ('\n', file); } name = dst_name; } switch_to_section (function_section (decl)); ASM_OUTPUT_FUNCTION_LABEL (file, name, decl); } struct gcc_target targetm = TARGET_INITIALIZER;
Go to most recent revision | Compare with Previous | Blame | View Log