diff options
Diffstat (limited to 'gcc/config/epiphany/epiphany.c')
-rwxr-xr-x | gcc/config/epiphany/epiphany.c | 2751 |
1 files changed, 2751 insertions, 0 deletions
diff --git a/gcc/config/epiphany/epiphany.c b/gcc/config/epiphany/epiphany.c new file mode 100755 index 00000000000..a4652da87b8 --- /dev/null +++ b/gcc/config/epiphany/epiphany.c @@ -0,0 +1,2751 @@ +/* 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 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", 1, 1, true, false, false, epiphany_handle_interrupt_attribute, true }, + { "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, false }, + { 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 = 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), "timer") + && strcmp (TREE_STRING_POINTER (value), "dma0") + && strcmp (TREE_STRING_POINTER (value), "dma1") + && strcmp (TREE_STRING_POINTER (value), "static_flag") + && strcmp (TREE_STRING_POINTER (value), "swi")) + { + warning (OPT_Wattributes, + "argument of %qE attribute is not \"reset\", \"software_exception\", \"timer\", \"dma0\", \"dma1\", \"static_flag\" or \"swi\"", + 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), args = TREE_VALUE (a); + + if (name == get_identifier ("interrupt") + && list_length (args) == 1 + && TREE_CODE (TREE_VALUE (args)) == STRING_CST) + { + tree value = TREE_VALUE (args); + + if (!strcmp (TREE_STRING_POINTER (value), "reset")) + fn_type = EPIPHANY_FUNCTION_RESET; + else if (!strcmp (TREE_STRING_POINTER (value), "software_exception")) + fn_type = EPIPHANY_FUNCTION_SOFTWARE_EXCEPTION; + else if (!strcmp (TREE_STRING_POINTER (value), "timer")) + fn_type = EPIPHANY_FUNCTION_TIMER; + else if (!strcmp (TREE_STRING_POINTER (value), "dma0")) + fn_type = EPIPHANY_FUNCTION_DMA0; + else if (!strcmp (TREE_STRING_POINTER (value), "dma1")) + fn_type = EPIPHANY_FUNCTION_DMA1; + else if (!strcmp (TREE_STRING_POINTER (value), "static_flag")) + fn_type = EPIPHANY_FUNCTION_STATIC_FLAG; + else if (!strcmp (TREE_STRING_POINTER (value), "swi")) + fn_type = EPIPHANY_FUNCTION_SWI; + else + gcc_unreachable (); + break; + } + } + + 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); + 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) + { + frame_move_insn (gen_rtx_REG (word_mode, STATUS_REGNUM), + gen_rtx_REG (SImode, GPR_0)); + frame_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); + frame_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); + } +} + +struct gcc_target targetm = TARGET_INITIALIZER; |