| /* Definitions for Toshiba Media Processor |
| Copyright (C) 2001-2013 Free Software Foundation, Inc. |
| Contributed by Red Hat, 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 "rtl.h" |
| #include "tree.h" |
| #include "regs.h" |
| #include "hard-reg-set.h" |
| #include "insn-config.h" |
| #include "conditions.h" |
| #include "insn-flags.h" |
| #include "output.h" |
| #include "insn-attr.h" |
| #include "flags.h" |
| #include "recog.h" |
| #include "obstack.h" |
| #include "tree.h" |
| #include "expr.h" |
| #include "except.h" |
| #include "function.h" |
| #include "optabs.h" |
| #include "reload.h" |
| #include "tm_p.h" |
| #include "ggc.h" |
| #include "diagnostic-core.h" |
| #include "target.h" |
| #include "target-def.h" |
| #include "langhooks.h" |
| #include "df.h" |
| #include "gimple.h" |
| #include "opts.h" |
| #include "dumpfile.h" |
| |
| /* Structure of this file: |
| |
| + Command Line Option Support |
| + Pattern support - constraints, predicates, expanders |
| + Reload Support |
| + Costs |
| + Functions to save and restore machine-specific function data. |
| + Frame/Epilog/Prolog Related |
| + Operand Printing |
| + Function args in registers |
| + Handle pipeline hazards |
| + Handle attributes |
| + Trampolines |
| + Machine-dependent Reorg |
| + Builtins. */ |
| |
| /* Symbol encodings: |
| |
| Symbols are encoded as @ <char> . <name> where <char> is one of these: |
| |
| b - based |
| t - tiny |
| n - near |
| f - far |
| i - io, near |
| I - io, far |
| c - cb (control bus) */ |
| |
| struct GTY(()) machine_function |
| { |
| int mep_frame_pointer_needed; |
| |
| /* For varargs. */ |
| int arg_regs_to_save; |
| int regsave_filler; |
| int frame_filler; |
| int frame_locked; |
| |
| /* Records __builtin_return address. */ |
| rtx eh_stack_adjust; |
| |
| int reg_save_size; |
| int reg_save_slot[FIRST_PSEUDO_REGISTER]; |
| unsigned char reg_saved[FIRST_PSEUDO_REGISTER]; |
| |
| /* 2 if the current function has an interrupt attribute, 1 if not, 0 |
| if unknown. This is here because resource.c uses EPILOGUE_USES |
| which needs it. */ |
| int interrupt_handler; |
| |
| /* Likewise, for disinterrupt attribute. */ |
| int disable_interrupts; |
| |
| /* Number of doloop tags used so far. */ |
| int doloop_tags; |
| |
| /* True if the last tag was allocated to a doloop_end. */ |
| bool doloop_tag_from_end; |
| |
| /* True if reload changes $TP. */ |
| bool reload_changes_tp; |
| |
| /* 2 if there are asm()s without operands, 1 if not, 0 if unknown. |
| We only set this if the function is an interrupt handler. */ |
| int asms_without_operands; |
| }; |
| |
| #define MEP_CONTROL_REG(x) \ |
| (GET_CODE (x) == REG && ANY_CONTROL_REGNO_P (REGNO (x))) |
| |
| static GTY(()) section * based_section; |
| static GTY(()) section * tinybss_section; |
| static GTY(()) section * far_section; |
| static GTY(()) section * farbss_section; |
| static GTY(()) section * frodata_section; |
| static GTY(()) section * srodata_section; |
| |
| static GTY(()) section * vtext_section; |
| static GTY(()) section * vftext_section; |
| static GTY(()) section * ftext_section; |
| |
| static void mep_set_leaf_registers (int); |
| static bool symbol_p (rtx); |
| static bool symbolref_p (rtx); |
| static void encode_pattern_1 (rtx); |
| static void encode_pattern (rtx); |
| static bool const_in_range (rtx, int, int); |
| static void mep_rewrite_mult (rtx, rtx); |
| static void mep_rewrite_mulsi3 (rtx, rtx, rtx, rtx); |
| static void mep_rewrite_maddsi3 (rtx, rtx, rtx, rtx, rtx); |
| static bool mep_reuse_lo_p_1 (rtx, rtx, rtx, bool); |
| static bool move_needs_splitting (rtx, rtx, enum machine_mode); |
| static bool mep_expand_setcc_1 (enum rtx_code, rtx, rtx, rtx); |
| static bool mep_nongeneral_reg (rtx); |
| static bool mep_general_copro_reg (rtx); |
| static bool mep_nonregister (rtx); |
| static struct machine_function* mep_init_machine_status (void); |
| static rtx mep_tp_rtx (void); |
| static rtx mep_gp_rtx (void); |
| static bool mep_interrupt_p (void); |
| static bool mep_disinterrupt_p (void); |
| static bool mep_reg_set_p (rtx, rtx); |
| static bool mep_reg_set_in_function (int); |
| static bool mep_interrupt_saved_reg (int); |
| static bool mep_call_saves_register (int); |
| static rtx F (rtx); |
| static void add_constant (int, int, int, int); |
| static rtx maybe_dead_move (rtx, rtx, bool); |
| static void mep_reload_pointer (int, const char *); |
| static void mep_start_function (FILE *, HOST_WIDE_INT); |
| static bool mep_function_ok_for_sibcall (tree, tree); |
| static int unique_bit_in (HOST_WIDE_INT); |
| static int bit_size_for_clip (HOST_WIDE_INT); |
| static int bytesize (const_tree, enum machine_mode); |
| static tree mep_validate_based_tiny (tree *, tree, tree, int, bool *); |
| static tree mep_validate_near_far (tree *, tree, tree, int, bool *); |
| static tree mep_validate_disinterrupt (tree *, tree, tree, int, bool *); |
| static tree mep_validate_interrupt (tree *, tree, tree, int, bool *); |
| static tree mep_validate_io_cb (tree *, tree, tree, int, bool *); |
| static tree mep_validate_vliw (tree *, tree, tree, int, bool *); |
| static bool mep_function_attribute_inlinable_p (const_tree); |
| static bool mep_can_inline_p (tree, tree); |
| static bool mep_lookup_pragma_disinterrupt (const char *); |
| static int mep_multiple_address_regions (tree, bool); |
| static int mep_attrlist_to_encoding (tree, tree); |
| static void mep_insert_attributes (tree, tree *); |
| static void mep_encode_section_info (tree, rtx, int); |
| static section * mep_select_section (tree, int, unsigned HOST_WIDE_INT); |
| static void mep_unique_section (tree, int); |
| static unsigned int mep_section_type_flags (tree, const char *, int); |
| static void mep_asm_named_section (const char *, unsigned int, tree); |
| static bool mep_mentioned_p (rtx, rtx, int); |
| static void mep_reorg_regmove (rtx); |
| static rtx mep_insert_repeat_label_last (rtx, rtx, bool, bool); |
| static void mep_reorg_repeat (rtx); |
| static bool mep_invertable_branch_p (rtx); |
| static void mep_invert_branch (rtx, rtx); |
| static void mep_reorg_erepeat (rtx); |
| static void mep_jmp_return_reorg (rtx); |
| static void mep_reorg_addcombine (rtx); |
| static void mep_reorg (void); |
| static void mep_init_intrinsics (void); |
| static void mep_init_builtins (void); |
| static void mep_intrinsic_unavailable (int); |
| static bool mep_get_intrinsic_insn (int, const struct cgen_insn **); |
| static bool mep_get_move_insn (int, const struct cgen_insn **); |
| static rtx mep_convert_arg (enum machine_mode, rtx); |
| static rtx mep_convert_regnum (const struct cgen_regnum_operand *, rtx); |
| static rtx mep_legitimize_arg (const struct insn_operand_data *, rtx, int); |
| static void mep_incompatible_arg (const struct insn_operand_data *, rtx, int, tree); |
| static rtx mep_expand_builtin (tree, rtx, rtx, enum machine_mode, int); |
| static int mep_adjust_cost (rtx, rtx, rtx, int); |
| static int mep_issue_rate (void); |
| static rtx mep_find_ready_insn (rtx *, int, enum attr_slot, int); |
| static void mep_move_ready_insn (rtx *, int, rtx); |
| static int mep_sched_reorder (FILE *, int, rtx *, int *, int); |
| static rtx mep_make_bundle (rtx, rtx); |
| static void mep_bundle_insns (rtx); |
| static bool mep_rtx_cost (rtx, int, int, int, int *, bool); |
| static int mep_address_cost (rtx, enum machine_mode, addr_space_t, bool); |
| static void mep_setup_incoming_varargs (cumulative_args_t, enum machine_mode, |
| tree, int *, int); |
| static bool mep_pass_by_reference (cumulative_args_t cum, enum machine_mode, |
| const_tree, bool); |
| static rtx mep_function_arg (cumulative_args_t, enum machine_mode, |
| const_tree, bool); |
| static void mep_function_arg_advance (cumulative_args_t, enum machine_mode, |
| const_tree, bool); |
| static bool mep_vector_mode_supported_p (enum machine_mode); |
| static rtx mep_allocate_initial_value (rtx); |
| static void mep_asm_init_sections (void); |
| static int mep_comp_type_attributes (const_tree, const_tree); |
| static bool mep_narrow_volatile_bitfield (void); |
| static rtx mep_expand_builtin_saveregs (void); |
| static tree mep_build_builtin_va_list (void); |
| static void mep_expand_va_start (tree, rtx); |
| static tree mep_gimplify_va_arg_expr (tree, tree, gimple_seq *, gimple_seq *); |
| static bool mep_can_eliminate (const int, const int); |
| static void mep_conditional_register_usage (void); |
| static void mep_trampoline_init (rtx, tree, rtx); |
| |
| #define WANT_GCC_DEFINITIONS |
| #include "mep-intrin.h" |
| #undef WANT_GCC_DEFINITIONS |
| |
| |
| /* Command Line Option Support. */ |
| |
| char mep_leaf_registers [FIRST_PSEUDO_REGISTER]; |
| |
| /* True if we can use cmov instructions to move values back and forth |
| between core and coprocessor registers. */ |
| bool mep_have_core_copro_moves_p; |
| |
| /* True if we can use cmov instructions (or a work-alike) to move |
| values between coprocessor registers. */ |
| bool mep_have_copro_copro_moves_p; |
| |
| /* A table of all coprocessor instructions that can act like |
| a coprocessor-to-coprocessor cmov. */ |
| static const int mep_cmov_insns[] = { |
| mep_cmov, |
| mep_cpmov, |
| mep_fmovs, |
| mep_caddi3, |
| mep_csubi3, |
| mep_candi3, |
| mep_cori3, |
| mep_cxori3, |
| mep_cand3, |
| mep_cor3 |
| }; |
| |
| |
| static void |
| mep_set_leaf_registers (int enable) |
| { |
| int i; |
| |
| if (mep_leaf_registers[0] != enable) |
| for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) |
| mep_leaf_registers[i] = enable; |
| } |
| |
| static void |
| mep_conditional_register_usage (void) |
| { |
| int i; |
| |
| if (!TARGET_OPT_MULT && !TARGET_OPT_DIV) |
| { |
| fixed_regs[HI_REGNO] = 1; |
| fixed_regs[LO_REGNO] = 1; |
| call_used_regs[HI_REGNO] = 1; |
| call_used_regs[LO_REGNO] = 1; |
| } |
| |
| for (i = FIRST_SHADOW_REGISTER; i <= LAST_SHADOW_REGISTER; i++) |
| global_regs[i] = 1; |
| } |
| |
| static void |
| mep_option_override (void) |
| { |
| unsigned int i; |
| int j; |
| cl_deferred_option *opt; |
| vec<cl_deferred_option> *v = (vec<cl_deferred_option> *) mep_deferred_options; |
| |
| if (v) |
| FOR_EACH_VEC_ELT (*v, i, opt) |
| { |
| switch (opt->opt_index) |
| { |
| case OPT_mivc2: |
| for (j = 0; j < 32; j++) |
| fixed_regs[j + 48] = 0; |
| for (j = 0; j < 32; j++) |
| call_used_regs[j + 48] = 1; |
| for (j = 6; j < 8; j++) |
| call_used_regs[j + 48] = 0; |
| |
| #define RN(n,s) reg_names[FIRST_CCR_REGNO + n] = s |
| RN (0, "$csar0"); |
| RN (1, "$cc"); |
| RN (4, "$cofr0"); |
| RN (5, "$cofr1"); |
| RN (6, "$cofa0"); |
| RN (7, "$cofa1"); |
| RN (15, "$csar1"); |
| |
| RN (16, "$acc0_0"); |
| RN (17, "$acc0_1"); |
| RN (18, "$acc0_2"); |
| RN (19, "$acc0_3"); |
| RN (20, "$acc0_4"); |
| RN (21, "$acc0_5"); |
| RN (22, "$acc0_6"); |
| RN (23, "$acc0_7"); |
| |
| RN (24, "$acc1_0"); |
| RN (25, "$acc1_1"); |
| RN (26, "$acc1_2"); |
| RN (27, "$acc1_3"); |
| RN (28, "$acc1_4"); |
| RN (29, "$acc1_5"); |
| RN (30, "$acc1_6"); |
| RN (31, "$acc1_7"); |
| #undef RN |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| if (flag_pic == 1) |
| warning (OPT_fpic, "-fpic is not supported"); |
| if (flag_pic == 2) |
| warning (OPT_fPIC, "-fPIC is not supported"); |
| if (TARGET_S && TARGET_M) |
| error ("only one of -ms and -mm may be given"); |
| if (TARGET_S && TARGET_L) |
| error ("only one of -ms and -ml may be given"); |
| if (TARGET_M && TARGET_L) |
| error ("only one of -mm and -ml may be given"); |
| if (TARGET_S && global_options_set.x_mep_tiny_cutoff) |
| error ("only one of -ms and -mtiny= may be given"); |
| if (TARGET_M && global_options_set.x_mep_tiny_cutoff) |
| error ("only one of -mm and -mtiny= may be given"); |
| if (TARGET_OPT_CLIP && ! TARGET_OPT_MINMAX) |
| warning (0, "-mclip currently has no effect without -mminmax"); |
| |
| if (mep_const_section) |
| { |
| if (strcmp (mep_const_section, "tiny") != 0 |
| && strcmp (mep_const_section, "near") != 0 |
| && strcmp (mep_const_section, "far") != 0) |
| error ("-mc= must be -mc=tiny, -mc=near, or -mc=far"); |
| } |
| |
| if (TARGET_S) |
| mep_tiny_cutoff = 65536; |
| if (TARGET_M) |
| mep_tiny_cutoff = 0; |
| if (TARGET_L && ! global_options_set.x_mep_tiny_cutoff) |
| mep_tiny_cutoff = 0; |
| |
| if (TARGET_64BIT_CR_REGS) |
| flag_split_wide_types = 0; |
| |
| init_machine_status = mep_init_machine_status; |
| mep_init_intrinsics (); |
| } |
| |
| /* Pattern Support - constraints, predicates, expanders. */ |
| |
| /* MEP has very few instructions that can refer to the span of |
| addresses used by symbols, so it's common to check for them. */ |
| |
| static bool |
| symbol_p (rtx x) |
| { |
| int c = GET_CODE (x); |
| |
| return (c == CONST_INT |
| || c == CONST |
| || c == SYMBOL_REF); |
| } |
| |
| static bool |
| symbolref_p (rtx x) |
| { |
| int c; |
| |
| if (GET_CODE (x) != MEM) |
| return false; |
| |
| c = GET_CODE (XEXP (x, 0)); |
| return (c == CONST_INT |
| || c == CONST |
| || c == SYMBOL_REF); |
| } |
| |
| /* static const char *reg_class_names[] = REG_CLASS_NAMES; */ |
| |
| #define GEN_REG(R, STRICT) \ |
| (GR_REGNO_P (R) \ |
| || (!STRICT \ |
| && ((R) == ARG_POINTER_REGNUM \ |
| || (R) >= FIRST_PSEUDO_REGISTER))) |
| |
| static char pattern[12], *patternp; |
| static GTY(()) rtx patternr[12]; |
| #define RTX_IS(x) (strcmp (pattern, x) == 0) |
| |
| static void |
| encode_pattern_1 (rtx x) |
| { |
| int i; |
| |
| if (patternp == pattern + sizeof (pattern) - 2) |
| { |
| patternp[-1] = '?'; |
| return; |
| } |
| |
| patternr[patternp-pattern] = x; |
| |
| switch (GET_CODE (x)) |
| { |
| case REG: |
| *patternp++ = 'r'; |
| break; |
| case MEM: |
| *patternp++ = 'm'; |
| case CONST: |
| encode_pattern_1 (XEXP(x, 0)); |
| break; |
| case PLUS: |
| *patternp++ = '+'; |
| encode_pattern_1 (XEXP(x, 0)); |
| encode_pattern_1 (XEXP(x, 1)); |
| break; |
| case LO_SUM: |
| *patternp++ = 'L'; |
| encode_pattern_1 (XEXP(x, 0)); |
| encode_pattern_1 (XEXP(x, 1)); |
| break; |
| case HIGH: |
| *patternp++ = 'H'; |
| encode_pattern_1 (XEXP(x, 0)); |
| break; |
| case SYMBOL_REF: |
| *patternp++ = 's'; |
| break; |
| case LABEL_REF: |
| *patternp++ = 'l'; |
| break; |
| case CONST_INT: |
| case CONST_DOUBLE: |
| *patternp++ = 'i'; |
| break; |
| case UNSPEC: |
| *patternp++ = 'u'; |
| *patternp++ = '0' + XCINT(x, 1, UNSPEC); |
| for (i=0; i<XVECLEN (x, 0); i++) |
| encode_pattern_1 (XVECEXP (x, 0, i)); |
| break; |
| case USE: |
| *patternp++ = 'U'; |
| break; |
| default: |
| *patternp++ = '?'; |
| #if 0 |
| fprintf (stderr, "can't encode pattern %s\n", GET_RTX_NAME(GET_CODE(x))); |
| debug_rtx (x); |
| gcc_unreachable (); |
| #endif |
| break; |
| } |
| } |
| |
| static void |
| encode_pattern (rtx x) |
| { |
| patternp = pattern; |
| encode_pattern_1 (x); |
| *patternp = 0; |
| } |
| |
| int |
| mep_section_tag (rtx x) |
| { |
| const char *name; |
| |
| while (1) |
| { |
| switch (GET_CODE (x)) |
| { |
| case MEM: |
| case CONST: |
| x = XEXP (x, 0); |
| break; |
| case UNSPEC: |
| x = XVECEXP (x, 0, 0); |
| break; |
| case PLUS: |
| if (GET_CODE (XEXP (x, 1)) != CONST_INT) |
| return 0; |
| x = XEXP (x, 0); |
| break; |
| default: |
| goto done; |
| } |
| } |
| done: |
| if (GET_CODE (x) != SYMBOL_REF) |
| return 0; |
| name = XSTR (x, 0); |
| if (name[0] == '@' && name[2] == '.') |
| { |
| if (name[1] == 'i' || name[1] == 'I') |
| { |
| if (name[1] == 'I') |
| return 'f'; /* near */ |
| return 'n'; /* far */ |
| } |
| return name[1]; |
| } |
| return 0; |
| } |
| |
| int |
| mep_regno_reg_class (int regno) |
| { |
| switch (regno) |
| { |
| case SP_REGNO: return SP_REGS; |
| case TP_REGNO: return TP_REGS; |
| case GP_REGNO: return GP_REGS; |
| case 0: return R0_REGS; |
| case HI_REGNO: return HI_REGS; |
| case LO_REGNO: return LO_REGS; |
| case ARG_POINTER_REGNUM: return GENERAL_REGS; |
| } |
| |
| if (GR_REGNO_P (regno)) |
| return regno < FIRST_GR_REGNO + 8 ? TPREL_REGS : GENERAL_REGS; |
| if (CONTROL_REGNO_P (regno)) |
| return CONTROL_REGS; |
| |
| if (CR_REGNO_P (regno)) |
| { |
| int i, j; |
| |
| /* Search for the register amongst user-defined subclasses of |
| the coprocessor registers. */ |
| for (i = USER0_REGS; i <= USER3_REGS; ++i) |
| { |
| if (! TEST_HARD_REG_BIT (reg_class_contents[i], regno)) |
| continue; |
| for (j = 0; j < N_REG_CLASSES; ++j) |
| { |
| enum reg_class sub = reg_class_subclasses[i][j]; |
| |
| if (sub == LIM_REG_CLASSES) |
| return i; |
| if (TEST_HARD_REG_BIT (reg_class_contents[sub], regno)) |
| break; |
| } |
| } |
| |
| return LOADABLE_CR_REGNO_P (regno) ? LOADABLE_CR_REGS : CR_REGS; |
| } |
| |
| if (CCR_REGNO_P (regno)) |
| return CCR_REGS; |
| |
| gcc_assert (regno >= FIRST_SHADOW_REGISTER && regno <= LAST_SHADOW_REGISTER); |
| return NO_REGS; |
| } |
| |
| static bool |
| const_in_range (rtx x, int minv, int maxv) |
| { |
| return (GET_CODE (x) == CONST_INT |
| && INTVAL (x) >= minv |
| && INTVAL (x) <= maxv); |
| } |
| |
| /* Given three integer registers DEST, SRC1 and SRC2, return an rtx X |
| such that "mulr DEST,X" will calculate DEST = SRC1 * SRC2. If a move |
| is needed, emit it before INSN if INSN is nonnull, otherwise emit it |
| at the end of the insn stream. */ |
| |
| rtx |
| mep_mulr_source (rtx insn, rtx dest, rtx src1, rtx src2) |
| { |
| if (rtx_equal_p (dest, src1)) |
| return src2; |
| else if (rtx_equal_p (dest, src2)) |
| return src1; |
| else |
| { |
| if (insn == 0) |
| emit_insn (gen_movsi (copy_rtx (dest), src1)); |
| else |
| emit_insn_before (gen_movsi (copy_rtx (dest), src1), insn); |
| return src2; |
| } |
| } |
| |
| /* Replace INSN's pattern with PATTERN, a multiplication PARALLEL. |
| Change the last element of PATTERN from (clobber (scratch:SI)) |
| to (clobber (reg:SI HI_REGNO)). */ |
| |
| static void |
| mep_rewrite_mult (rtx insn, rtx pattern) |
| { |
| rtx hi_clobber; |
| |
| hi_clobber = XVECEXP (pattern, 0, XVECLEN (pattern, 0) - 1); |
| XEXP (hi_clobber, 0) = gen_rtx_REG (SImode, HI_REGNO); |
| PATTERN (insn) = pattern; |
| INSN_CODE (insn) = -1; |
| } |
| |
| /* Subroutine of mep_reuse_lo_p. Rewrite instruction INSN so that it |
| calculates SRC1 * SRC2 and stores the result in $lo. Also make it |
| store the result in DEST if nonnull. */ |
| |
| static void |
| mep_rewrite_mulsi3 (rtx insn, rtx dest, rtx src1, rtx src2) |
| { |
| rtx lo, pattern; |
| |
| lo = gen_rtx_REG (SImode, LO_REGNO); |
| if (dest) |
| pattern = gen_mulsi3r (lo, dest, copy_rtx (dest), |
| mep_mulr_source (insn, dest, src1, src2)); |
| else |
| pattern = gen_mulsi3_lo (lo, src1, src2); |
| mep_rewrite_mult (insn, pattern); |
| } |
| |
| /* Like mep_rewrite_mulsi3, but calculate SRC1 * SRC2 + SRC3. First copy |
| SRC3 into $lo, then use either madd or maddr. The move into $lo will |
| be deleted by a peephole2 if SRC3 is already in $lo. */ |
| |
| static void |
| mep_rewrite_maddsi3 (rtx insn, rtx dest, rtx src1, rtx src2, rtx src3) |
| { |
| rtx lo, pattern; |
| |
| lo = gen_rtx_REG (SImode, LO_REGNO); |
| emit_insn_before (gen_movsi (copy_rtx (lo), src3), insn); |
| if (dest) |
| pattern = gen_maddsi3r (lo, dest, copy_rtx (dest), |
| mep_mulr_source (insn, dest, src1, src2), |
| copy_rtx (lo)); |
| else |
| pattern = gen_maddsi3_lo (lo, src1, src2, copy_rtx (lo)); |
| mep_rewrite_mult (insn, pattern); |
| } |
| |
| /* Return true if $lo has the same value as integer register GPR when |
| instruction INSN is reached. If necessary, rewrite the instruction |
| that sets $lo so that it uses a proper SET, not a CLOBBER. LO is an |
| rtx for (reg:SI LO_REGNO). |
| |
| This function is intended to be used by the peephole2 pass. Since |
| that pass goes from the end of a basic block to the beginning, and |
| propagates liveness information on the way, there is no need to |
| update register notes here. |
| |
| If GPR_DEAD_P is true on entry, and this function returns true, |
| then the caller will replace _every_ use of GPR in and after INSN |
| with LO. This means that if the instruction that sets $lo is a |
| mulr- or maddr-type instruction, we can rewrite it to use mul or |
| madd instead. In combination with the copy progagation pass, |
| this allows us to replace sequences like: |
| |
| mov GPR,R1 |
| mulr GPR,R2 |
| |
| with: |
| |
| mul R1,R2 |
| |
| if GPR is no longer used. */ |
| |
| static bool |
| mep_reuse_lo_p_1 (rtx lo, rtx gpr, rtx insn, bool gpr_dead_p) |
| { |
| do |
| { |
| insn = PREV_INSN (insn); |
| if (INSN_P (insn)) |
| switch (recog_memoized (insn)) |
| { |
| case CODE_FOR_mulsi3_1: |
| extract_insn (insn); |
| if (rtx_equal_p (recog_data.operand[0], gpr)) |
| { |
| mep_rewrite_mulsi3 (insn, |
| gpr_dead_p ? NULL : recog_data.operand[0], |
| recog_data.operand[1], |
| recog_data.operand[2]); |
| return true; |
| } |
| return false; |
| |
| case CODE_FOR_maddsi3: |
| extract_insn (insn); |
| if (rtx_equal_p (recog_data.operand[0], gpr)) |
| { |
| mep_rewrite_maddsi3 (insn, |
| gpr_dead_p ? NULL : recog_data.operand[0], |
| recog_data.operand[1], |
| recog_data.operand[2], |
| recog_data.operand[3]); |
| return true; |
| } |
| return false; |
| |
| case CODE_FOR_mulsi3r: |
| case CODE_FOR_maddsi3r: |
| extract_insn (insn); |
| return rtx_equal_p (recog_data.operand[1], gpr); |
| |
| default: |
| if (reg_set_p (lo, insn) |
| || reg_set_p (gpr, insn) |
| || volatile_insn_p (PATTERN (insn))) |
| return false; |
| |
| if (gpr_dead_p && reg_referenced_p (gpr, PATTERN (insn))) |
| gpr_dead_p = false; |
| break; |
| } |
| } |
| while (!NOTE_INSN_BASIC_BLOCK_P (insn)); |
| return false; |
| } |
| |
| /* A wrapper around mep_reuse_lo_p_1 that preserves recog_data. */ |
| |
| bool |
| mep_reuse_lo_p (rtx lo, rtx gpr, rtx insn, bool gpr_dead_p) |
| { |
| bool result = mep_reuse_lo_p_1 (lo, gpr, insn, gpr_dead_p); |
| extract_insn (insn); |
| return result; |
| } |
| |
| /* Return true if SET can be turned into a post-modify load or store |
| that adds OFFSET to GPR. In other words, return true if SET can be |
| changed into: |
| |
| (parallel [SET (set GPR (plus:SI GPR OFFSET))]). |
| |
| It's OK to change SET to an equivalent operation in order to |
| make it match. */ |
| |
| static bool |
| mep_use_post_modify_for_set_p (rtx set, rtx gpr, rtx offset) |
| { |
| rtx *reg, *mem; |
| unsigned int reg_bytes, mem_bytes; |
| enum machine_mode reg_mode, mem_mode; |
| |
| /* Only simple SETs can be converted. */ |
| if (GET_CODE (set) != SET) |
| return false; |
| |
| /* Point REG to what we hope will be the register side of the set and |
| MEM to what we hope will be the memory side. */ |
| if (GET_CODE (SET_DEST (set)) == MEM) |
| { |
| mem = &SET_DEST (set); |
| reg = &SET_SRC (set); |
| } |
| else |
| { |
| reg = &SET_DEST (set); |
| mem = &SET_SRC (set); |
| if (GET_CODE (*mem) == SIGN_EXTEND) |
| mem = &XEXP (*mem, 0); |
| } |
| |
| /* Check that *REG is a suitable coprocessor register. */ |
| if (GET_CODE (*reg) != REG || !LOADABLE_CR_REGNO_P (REGNO (*reg))) |
| return false; |
| |
| /* Check that *MEM is a suitable memory reference. */ |
| if (GET_CODE (*mem) != MEM || !rtx_equal_p (XEXP (*mem, 0), gpr)) |
| return false; |
| |
| /* Get the number of bytes in each operand. */ |
| mem_bytes = GET_MODE_SIZE (GET_MODE (*mem)); |
| reg_bytes = GET_MODE_SIZE (GET_MODE (*reg)); |
| |
| /* Check that OFFSET is suitably aligned. */ |
| if (INTVAL (offset) & (mem_bytes - 1)) |
| return false; |
| |
| /* Convert *MEM to a normal integer mode. */ |
| mem_mode = mode_for_size (mem_bytes * BITS_PER_UNIT, MODE_INT, 0); |
| *mem = change_address (*mem, mem_mode, NULL); |
| |
| /* Adjust *REG as well. */ |
| *reg = shallow_copy_rtx (*reg); |
| if (reg == &SET_DEST (set) && reg_bytes < UNITS_PER_WORD) |
| { |
| /* SET is a subword load. Convert it to an explicit extension. */ |
| PUT_MODE (*reg, SImode); |
| *mem = gen_rtx_SIGN_EXTEND (SImode, *mem); |
| } |
| else |
| { |
| reg_mode = mode_for_size (reg_bytes * BITS_PER_UNIT, MODE_INT, 0); |
| PUT_MODE (*reg, reg_mode); |
| } |
| return true; |
| } |
| |
| /* Return the effect of frame-related instruction INSN. */ |
| |
| static rtx |
| mep_frame_expr (rtx insn) |
| { |
| rtx note, expr; |
| |
| note = find_reg_note (insn, REG_FRAME_RELATED_EXPR, 0); |
| expr = (note != 0 ? XEXP (note, 0) : copy_rtx (PATTERN (insn))); |
| RTX_FRAME_RELATED_P (expr) = 1; |
| return expr; |
| } |
| |
| /* Merge instructions INSN1 and INSN2 using a PARALLEL. Store the |
| new pattern in INSN1; INSN2 will be deleted by the caller. */ |
| |
| static void |
| mep_make_parallel (rtx insn1, rtx insn2) |
| { |
| rtx expr; |
| |
| if (RTX_FRAME_RELATED_P (insn2)) |
| { |
| expr = mep_frame_expr (insn2); |
| if (RTX_FRAME_RELATED_P (insn1)) |
| expr = gen_rtx_SEQUENCE (VOIDmode, |
| gen_rtvec (2, mep_frame_expr (insn1), expr)); |
| set_unique_reg_note (insn1, REG_FRAME_RELATED_EXPR, expr); |
| RTX_FRAME_RELATED_P (insn1) = 1; |
| } |
| |
| PATTERN (insn1) = gen_rtx_PARALLEL (VOIDmode, |
| gen_rtvec (2, PATTERN (insn1), |
| PATTERN (insn2))); |
| INSN_CODE (insn1) = -1; |
| } |
| |
| /* SET_INSN is an instruction that adds OFFSET to REG. Go back through |
| the basic block to see if any previous load or store instruction can |
| be persuaded to do SET_INSN as a side-effect. Return true if so. */ |
| |
| static bool |
| mep_use_post_modify_p_1 (rtx set_insn, rtx reg, rtx offset) |
| { |
| rtx insn; |
| |
| insn = set_insn; |
| do |
| { |
| insn = PREV_INSN (insn); |
| if (INSN_P (insn)) |
| { |
| if (mep_use_post_modify_for_set_p (PATTERN (insn), reg, offset)) |
| { |
| mep_make_parallel (insn, set_insn); |
| return true; |
| } |
| |
| if (reg_set_p (reg, insn) |
| || reg_referenced_p (reg, PATTERN (insn)) |
| || volatile_insn_p (PATTERN (insn))) |
| return false; |
| } |
| } |
| while (!NOTE_INSN_BASIC_BLOCK_P (insn)); |
| return false; |
| } |
| |
| /* A wrapper around mep_use_post_modify_p_1 that preserves recog_data. */ |
| |
| bool |
| mep_use_post_modify_p (rtx insn, rtx reg, rtx offset) |
| { |
| bool result = mep_use_post_modify_p_1 (insn, reg, offset); |
| extract_insn (insn); |
| return result; |
| } |
| |
| bool |
| mep_allow_clip (rtx ux, rtx lx, int s) |
| { |
| HOST_WIDE_INT u = INTVAL (ux); |
| HOST_WIDE_INT l = INTVAL (lx); |
| int i; |
| |
| if (!TARGET_OPT_CLIP) |
| return false; |
| |
| if (s) |
| { |
| for (i = 0; i < 30; i ++) |
| if ((u == ((HOST_WIDE_INT) 1 << i) - 1) |
| && (l == - ((HOST_WIDE_INT) 1 << i))) |
| return true; |
| } |
| else |
| { |
| if (l != 0) |
| return false; |
| |
| for (i = 0; i < 30; i ++) |
| if ((u == ((HOST_WIDE_INT) 1 << i) - 1)) |
| return true; |
| } |
| return false; |
| } |
| |
| bool |
| mep_bit_position_p (rtx x, bool looking_for) |
| { |
| if (GET_CODE (x) != CONST_INT) |
| return false; |
| switch ((int) INTVAL(x) & 0xff) |
| { |
| case 0x01: case 0x02: case 0x04: case 0x08: |
| case 0x10: case 0x20: case 0x40: case 0x80: |
| return looking_for; |
| case 0xfe: case 0xfd: case 0xfb: case 0xf7: |
| case 0xef: case 0xdf: case 0xbf: case 0x7f: |
| return !looking_for; |
| } |
| return false; |
| } |
| |
| static bool |
| move_needs_splitting (rtx dest, rtx src, |
| enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| int s = mep_section_tag (src); |
| |
| while (1) |
| { |
| if (GET_CODE (src) == CONST |
| || GET_CODE (src) == MEM) |
| src = XEXP (src, 0); |
| else if (GET_CODE (src) == SYMBOL_REF |
| || GET_CODE (src) == LABEL_REF |
| || GET_CODE (src) == PLUS) |
| break; |
| else |
| return false; |
| } |
| if (s == 'f' |
| || (GET_CODE (src) == PLUS |
| && GET_CODE (XEXP (src, 1)) == CONST_INT |
| && (INTVAL (XEXP (src, 1)) < -65536 |
| || INTVAL (XEXP (src, 1)) > 0xffffff)) |
| || (GET_CODE (dest) == REG |
| && REGNO (dest) > 7 && REGNO (dest) < FIRST_PSEUDO_REGISTER)) |
| return true; |
| return false; |
| } |
| |
| bool |
| mep_split_mov (rtx *operands, int symbolic) |
| { |
| if (symbolic) |
| { |
| if (move_needs_splitting (operands[0], operands[1], SImode)) |
| return true; |
| return false; |
| } |
| |
| if (GET_CODE (operands[1]) != CONST_INT) |
| return false; |
| |
| if (constraint_satisfied_p (operands[1], CONSTRAINT_I) |
| || constraint_satisfied_p (operands[1], CONSTRAINT_J) |
| || constraint_satisfied_p (operands[1], CONSTRAINT_O)) |
| return false; |
| |
| if (((!reload_completed && !reload_in_progress) |
| || (REG_P (operands[0]) && REGNO (operands[0]) < 8)) |
| && constraint_satisfied_p (operands[1], CONSTRAINT_K)) |
| return false; |
| |
| return true; |
| } |
| |
| /* Irritatingly, the "jsrv" insn *toggles* PSW.OM rather than set |
| it to one specific value. So the insn chosen depends on whether |
| the source and destination modes match. */ |
| |
| bool |
| mep_vliw_mode_match (rtx tgt) |
| { |
| bool src_vliw = mep_vliw_function_p (cfun->decl); |
| bool tgt_vliw = INTVAL (tgt); |
| |
| return src_vliw == tgt_vliw; |
| } |
| |
| /* Like the above, but also test for near/far mismatches. */ |
| |
| bool |
| mep_vliw_jmp_match (rtx tgt) |
| { |
| bool src_vliw = mep_vliw_function_p (cfun->decl); |
| bool tgt_vliw = INTVAL (tgt); |
| |
| if (mep_section_tag (DECL_RTL (cfun->decl)) == 'f') |
| return false; |
| |
| return src_vliw == tgt_vliw; |
| } |
| |
| bool |
| mep_multi_slot (rtx x) |
| { |
| return get_attr_slot (x) == SLOT_MULTI; |
| } |
| |
| /* Implement TARGET_LEGITIMATE_CONSTANT_P. */ |
| |
| static bool |
| mep_legitimate_constant_p (enum machine_mode mode ATTRIBUTE_UNUSED, rtx x) |
| { |
| /* We can't convert symbol values to gp- or tp-rel values after |
| reload, as reload might have used $gp or $tp for other |
| purposes. */ |
| if (GET_CODE (x) == SYMBOL_REF && (reload_in_progress || reload_completed)) |
| { |
| char e = mep_section_tag (x); |
| return (e != 't' && e != 'b'); |
| } |
| return 1; |
| } |
| |
| /* Be careful not to use macros that need to be compiled one way for |
| strict, and another way for not-strict, like REG_OK_FOR_BASE_P. */ |
| |
| bool |
| mep_legitimate_address (enum machine_mode mode, rtx x, int strict) |
| { |
| int the_tag; |
| |
| #define DEBUG_LEGIT 0 |
| #if DEBUG_LEGIT |
| fprintf (stderr, "legit: mode %s strict %d ", mode_name[mode], strict); |
| debug_rtx (x); |
| #endif |
| |
| if (GET_CODE (x) == LO_SUM |
| && GET_CODE (XEXP (x, 0)) == REG |
| && GEN_REG (REGNO (XEXP (x, 0)), strict) |
| && CONSTANT_P (XEXP (x, 1))) |
| { |
| if (GET_MODE_SIZE (mode) > 4) |
| { |
| /* We will end up splitting this, and lo_sums are not |
| offsettable for us. */ |
| #if DEBUG_LEGIT |
| fprintf(stderr, " - nope, %%lo(sym)[reg] not splittable\n"); |
| #endif |
| return false; |
| } |
| #if DEBUG_LEGIT |
| fprintf (stderr, " - yup, %%lo(sym)[reg]\n"); |
| #endif |
| return true; |
| } |
| |
| if (GET_CODE (x) == REG |
| && GEN_REG (REGNO (x), strict)) |
| { |
| #if DEBUG_LEGIT |
| fprintf (stderr, " - yup, [reg]\n"); |
| #endif |
| return true; |
| } |
| |
| if (GET_CODE (x) == PLUS |
| && GET_CODE (XEXP (x, 0)) == REG |
| && GEN_REG (REGNO (XEXP (x, 0)), strict) |
| && const_in_range (XEXP (x, 1), -32768, 32767)) |
| { |
| #if DEBUG_LEGIT |
| fprintf (stderr, " - yup, [reg+const]\n"); |
| #endif |
| return true; |
| } |
| |
| if (GET_CODE (x) == PLUS |
| && GET_CODE (XEXP (x, 0)) == REG |
| && GEN_REG (REGNO (XEXP (x, 0)), strict) |
| && GET_CODE (XEXP (x, 1)) == CONST |
| && (GET_CODE (XEXP (XEXP (x, 1), 0)) == UNSPEC |
| || (GET_CODE (XEXP (XEXP (x, 1), 0)) == PLUS |
| && GET_CODE (XEXP (XEXP (XEXP (x, 1), 0), 0)) == UNSPEC |
| && GET_CODE (XEXP (XEXP (XEXP (x, 1), 0), 1)) == CONST_INT))) |
| { |
| #if DEBUG_LEGIT |
| fprintf (stderr, " - yup, [reg+unspec]\n"); |
| #endif |
| return true; |
| } |
| |
| the_tag = mep_section_tag (x); |
| |
| if (the_tag == 'f') |
| { |
| #if DEBUG_LEGIT |
| fprintf (stderr, " - nope, [far]\n"); |
| #endif |
| return false; |
| } |
| |
| if (mode == VOIDmode |
| && GET_CODE (x) == SYMBOL_REF) |
| { |
| #if DEBUG_LEGIT |
| fprintf (stderr, " - yup, call [symbol]\n"); |
| #endif |
| return true; |
| } |
| |
| if ((mode == SImode || mode == SFmode) |
| && CONSTANT_P (x) |
| && mep_legitimate_constant_p (mode, x) |
| && the_tag != 't' && the_tag != 'b') |
| { |
| if (GET_CODE (x) != CONST_INT |
| || (INTVAL (x) <= 0xfffff |
| && INTVAL (x) >= 0 |
| && (INTVAL (x) % 4) == 0)) |
| { |
| #if DEBUG_LEGIT |
| fprintf (stderr, " - yup, [const]\n"); |
| #endif |
| return true; |
| } |
| } |
| |
| #if DEBUG_LEGIT |
| fprintf (stderr, " - nope.\n"); |
| #endif |
| return false; |
| } |
| |
| int |
| mep_legitimize_reload_address (rtx *x, enum machine_mode mode, int opnum, |
| int type_i, |
| int ind_levels ATTRIBUTE_UNUSED) |
| { |
| enum reload_type type = (enum reload_type) type_i; |
| |
| if (GET_CODE (*x) == PLUS |
| && GET_CODE (XEXP (*x, 0)) == MEM |
| && GET_CODE (XEXP (*x, 1)) == REG) |
| { |
| /* GCC will by default copy the MEM into a REG, which results in |
| an invalid address. For us, the best thing to do is move the |
| whole expression to a REG. */ |
| push_reload (*x, NULL_RTX, x, NULL, |
| GENERAL_REGS, mode, VOIDmode, |
| 0, 0, opnum, type); |
| return 1; |
| } |
| |
| if (GET_CODE (*x) == PLUS |
| && GET_CODE (XEXP (*x, 0)) == SYMBOL_REF |
| && GET_CODE (XEXP (*x, 1)) == CONST_INT) |
| { |
| char e = mep_section_tag (XEXP (*x, 0)); |
| |
| if (e != 't' && e != 'b') |
| { |
| /* GCC thinks that (sym+const) is a valid address. Well, |
| sometimes it is, this time it isn't. The best thing to |
| do is reload the symbol to a register, since reg+int |
| tends to work, and we can't just add the symbol and |
| constant anyway. */ |
| push_reload (XEXP (*x, 0), NULL_RTX, &(XEXP(*x, 0)), NULL, |
| GENERAL_REGS, mode, VOIDmode, |
| 0, 0, opnum, type); |
| return 1; |
| } |
| } |
| return 0; |
| } |
| |
| int |
| mep_core_address_length (rtx insn, int opn) |
| { |
| rtx set = single_set (insn); |
| rtx mem = XEXP (set, opn); |
| rtx other = XEXP (set, 1-opn); |
| rtx addr = XEXP (mem, 0); |
| |
| if (register_operand (addr, Pmode)) |
| return 2; |
| if (GET_CODE (addr) == PLUS) |
| { |
| rtx addend = XEXP (addr, 1); |
| |
| gcc_assert (REG_P (XEXP (addr, 0))); |
| |
| switch (REGNO (XEXP (addr, 0))) |
| { |
| case STACK_POINTER_REGNUM: |
| if (GET_MODE_SIZE (GET_MODE (mem)) == 4 |
| && mep_imm7a4_operand (addend, VOIDmode)) |
| return 2; |
| break; |
| |
| case 13: /* TP */ |
| gcc_assert (REG_P (other)); |
| |
| if (REGNO (other) >= 8) |
| break; |
| |
| if (GET_CODE (addend) == CONST |
| && GET_CODE (XEXP (addend, 0)) == UNSPEC |
| && XINT (XEXP (addend, 0), 1) == UNS_TPREL) |
| return 2; |
| |
| if (GET_CODE (addend) == CONST_INT |
| && INTVAL (addend) >= 0 |
| && INTVAL (addend) <= 127 |
| && INTVAL (addend) % GET_MODE_SIZE (GET_MODE (mem)) == 0) |
| return 2; |
| break; |
| } |
| } |
| |
| return 4; |
| } |
| |
| int |
| mep_cop_address_length (rtx insn, int opn) |
| { |
| rtx set = single_set (insn); |
| rtx mem = XEXP (set, opn); |
| rtx addr = XEXP (mem, 0); |
| |
| if (GET_CODE (mem) != MEM) |
| return 2; |
| if (register_operand (addr, Pmode)) |
| return 2; |
| if (GET_CODE (addr) == POST_INC) |
| return 2; |
| |
| return 4; |
| } |
| |
| #define DEBUG_EXPAND_MOV 0 |
| bool |
| mep_expand_mov (rtx *operands, enum machine_mode mode) |
| { |
| int i, t; |
| int tag[2]; |
| rtx tpsym, tpoffs; |
| int post_reload = 0; |
| |
| tag[0] = mep_section_tag (operands[0]); |
| tag[1] = mep_section_tag (operands[1]); |
| |
| if (!reload_in_progress |
| && !reload_completed |
| && GET_CODE (operands[0]) != REG |
| && GET_CODE (operands[0]) != SUBREG |
| && GET_CODE (operands[1]) != REG |
| && GET_CODE (operands[1]) != SUBREG) |
| operands[1] = copy_to_mode_reg (mode, operands[1]); |
| |
| #if DEBUG_EXPAND_MOV |
| fprintf(stderr, "expand move %s %d\n", mode_name[mode], |
| reload_in_progress || reload_completed); |
| debug_rtx (operands[0]); |
| debug_rtx (operands[1]); |
| #endif |
| |
| if (mode == DImode || mode == DFmode) |
| return false; |
| |
| if (reload_in_progress || reload_completed) |
| { |
| rtx r; |
| |
| if (GET_CODE (operands[0]) == REG && REGNO (operands[0]) == TP_REGNO) |
| cfun->machine->reload_changes_tp = true; |
| |
| if (tag[0] == 't' || tag[1] == 't') |
| { |
| r = has_hard_reg_initial_val (Pmode, GP_REGNO); |
| if (!r || GET_CODE (r) != REG || REGNO (r) != GP_REGNO) |
| post_reload = 1; |
| } |
| if (tag[0] == 'b' || tag[1] == 'b') |
| { |
| r = has_hard_reg_initial_val (Pmode, TP_REGNO); |
| if (!r || GET_CODE (r) != REG || REGNO (r) != TP_REGNO) |
| post_reload = 1; |
| } |
| if (cfun->machine->reload_changes_tp == true) |
| post_reload = 1; |
| } |
| |
| if (!post_reload) |
| { |
| rtx n; |
| if (symbol_p (operands[1])) |
| { |
| t = mep_section_tag (operands[1]); |
| if (t == 'b' || t == 't') |
| { |
| |
| if (GET_CODE (operands[1]) == SYMBOL_REF) |
| { |
| tpsym = operands[1]; |
| n = gen_rtx_UNSPEC (mode, |
| gen_rtvec (1, operands[1]), |
| t == 'b' ? UNS_TPREL : UNS_GPREL); |
| n = gen_rtx_CONST (mode, n); |
| } |
| else if (GET_CODE (operands[1]) == CONST |
| && GET_CODE (XEXP (operands[1], 0)) == PLUS |
| && GET_CODE (XEXP (XEXP (operands[1], 0), 0)) == SYMBOL_REF |
| && GET_CODE (XEXP (XEXP (operands[1], 0), 1)) == CONST_INT) |
| { |
| tpsym = XEXP (XEXP (operands[1], 0), 0); |
| tpoffs = XEXP (XEXP (operands[1], 0), 1); |
| n = gen_rtx_UNSPEC (mode, |
| gen_rtvec (1, tpsym), |
| t == 'b' ? UNS_TPREL : UNS_GPREL); |
| n = gen_rtx_PLUS (mode, n, tpoffs); |
| n = gen_rtx_CONST (mode, n); |
| } |
| else if (GET_CODE (operands[1]) == CONST |
| && GET_CODE (XEXP (operands[1], 0)) == UNSPEC) |
| return false; |
| else |
| { |
| error ("unusual TP-relative address"); |
| return false; |
| } |
| |
| n = gen_rtx_PLUS (mode, (t == 'b' ? mep_tp_rtx () |
| : mep_gp_rtx ()), n); |
| n = emit_insn (gen_rtx_SET (mode, operands[0], n)); |
| #if DEBUG_EXPAND_MOV |
| fprintf(stderr, "mep_expand_mov emitting "); |
| debug_rtx(n); |
| #endif |
| return true; |
| } |
| } |
| |
| for (i=0; i < 2; i++) |
| { |
| t = mep_section_tag (operands[i]); |
| if (GET_CODE (operands[i]) == MEM && (t == 'b' || t == 't')) |
| { |
| rtx sym, n, r; |
| int u; |
| |
| sym = XEXP (operands[i], 0); |
| if (GET_CODE (sym) == CONST |
| && GET_CODE (XEXP (sym, 0)) == UNSPEC) |
| sym = XVECEXP (XEXP (sym, 0), 0, 0); |
| |
| if (t == 'b') |
| { |
| r = mep_tp_rtx (); |
| u = UNS_TPREL; |
| } |
| else |
| { |
| r = mep_gp_rtx (); |
| u = UNS_GPREL; |
| } |
| |
| n = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, sym), u); |
| n = gen_rtx_CONST (Pmode, n); |
| n = gen_rtx_PLUS (Pmode, r, n); |
| operands[i] = replace_equiv_address (operands[i], n); |
| } |
| } |
| } |
| |
| if ((GET_CODE (operands[1]) != REG |
| && MEP_CONTROL_REG (operands[0])) |
| || (GET_CODE (operands[0]) != REG |
| && MEP_CONTROL_REG (operands[1]))) |
| { |
| rtx temp; |
| #if DEBUG_EXPAND_MOV |
| fprintf (stderr, "cr-mem, forcing op1 to reg\n"); |
| #endif |
| temp = gen_reg_rtx (mode); |
| emit_move_insn (temp, operands[1]); |
| operands[1] = temp; |
| } |
| |
| if (symbolref_p (operands[0]) |
| && (mep_section_tag (XEXP (operands[0], 0)) == 'f' |
| || (GET_MODE_SIZE (mode) != 4))) |
| { |
| rtx temp; |
| |
| gcc_assert (!reload_in_progress && !reload_completed); |
| |
| temp = force_reg (Pmode, XEXP (operands[0], 0)); |
| operands[0] = replace_equiv_address (operands[0], temp); |
| emit_move_insn (operands[0], operands[1]); |
| return true; |
| } |
| |
| if (!post_reload && (tag[1] == 't' || tag[1] == 'b')) |
| tag[1] = 0; |
| |
| if (symbol_p (operands[1]) |
| && (tag[1] == 'f' || tag[1] == 't' || tag[1] == 'b')) |
| { |
| emit_insn (gen_movsi_topsym_s (operands[0], operands[1])); |
| emit_insn (gen_movsi_botsym_s (operands[0], operands[0], operands[1])); |
| return true; |
| } |
| |
| if (symbolref_p (operands[1]) |
| && (tag[1] == 'f' || tag[1] == 't' || tag[1] == 'b')) |
| { |
| rtx temp; |
| |
| if (reload_in_progress || reload_completed) |
| temp = operands[0]; |
| else |
| temp = gen_reg_rtx (Pmode); |
| |
| emit_insn (gen_movsi_topsym_s (temp, operands[1])); |
| emit_insn (gen_movsi_botsym_s (temp, temp, operands[1])); |
| emit_move_insn (operands[0], replace_equiv_address (operands[1], temp)); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* Cases where the pattern can't be made to use at all. */ |
| |
| bool |
| mep_mov_ok (rtx *operands, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| int i; |
| |
| #define DEBUG_MOV_OK 0 |
| #if DEBUG_MOV_OK |
| fprintf (stderr, "mep_mov_ok %s %c=%c\n", mode_name[mode], mep_section_tag (operands[0]), |
| mep_section_tag (operands[1])); |
| debug_rtx (operands[0]); |
| debug_rtx (operands[1]); |
| #endif |
| |
| /* We want the movh patterns to get these. */ |
| if (GET_CODE (operands[1]) == HIGH) |
| return false; |
| |
| /* We can't store a register to a far variable without using a |
| scratch register to hold the address. Using far variables should |
| be split by mep_emit_mov anyway. */ |
| if (mep_section_tag (operands[0]) == 'f' |
| || mep_section_tag (operands[1]) == 'f') |
| { |
| #if DEBUG_MOV_OK |
| fprintf (stderr, " - no, f\n"); |
| #endif |
| return false; |
| } |
| i = mep_section_tag (operands[1]); |
| if ((i == 'b' || i == 't') && !reload_completed && !reload_in_progress) |
| /* These are supposed to be generated with adds of the appropriate |
| register. During and after reload, however, we allow them to |
| be accessed as normal symbols because adding a dependency on |
| the base register now might cause problems. */ |
| { |
| #if DEBUG_MOV_OK |
| fprintf (stderr, " - no, bt\n"); |
| #endif |
| return false; |
| } |
| |
| /* The only moves we can allow involve at least one general |
| register, so require it. */ |
| for (i = 0; i < 2; i ++) |
| { |
| /* Allow subregs too, before reload. */ |
| rtx x = operands[i]; |
| |
| if (GET_CODE (x) == SUBREG) |
| x = XEXP (x, 0); |
| if (GET_CODE (x) == REG |
| && ! MEP_CONTROL_REG (x)) |
| { |
| #if DEBUG_MOV_OK |
| fprintf (stderr, " - ok\n"); |
| #endif |
| return true; |
| } |
| } |
| #if DEBUG_MOV_OK |
| fprintf (stderr, " - no, no gen reg\n"); |
| #endif |
| return false; |
| } |
| |
| #define DEBUG_SPLIT_WIDE_MOVE 0 |
| void |
| mep_split_wide_move (rtx *operands, enum machine_mode mode) |
| { |
| int i; |
| |
| #if DEBUG_SPLIT_WIDE_MOVE |
| fprintf (stderr, "\n\033[34mmep_split_wide_move\033[0m mode %s\n", mode_name[mode]); |
| debug_rtx (operands[0]); |
| debug_rtx (operands[1]); |
| #endif |
| |
| for (i = 0; i <= 1; i++) |
| { |
| rtx op = operands[i], hi, lo; |
| |
| switch (GET_CODE (op)) |
| { |
| case REG: |
| { |
| unsigned int regno = REGNO (op); |
| |
| if (TARGET_64BIT_CR_REGS && CR_REGNO_P (regno)) |
| { |
| rtx i32; |
| |
| lo = gen_rtx_REG (SImode, regno); |
| i32 = GEN_INT (32); |
| hi = gen_rtx_ZERO_EXTRACT (SImode, |
| gen_rtx_REG (DImode, regno), |
| i32, i32); |
| } |
| else |
| { |
| hi = gen_rtx_REG (SImode, regno + TARGET_LITTLE_ENDIAN); |
| lo = gen_rtx_REG (SImode, regno + TARGET_BIG_ENDIAN); |
| } |
| } |
| break; |
| |
| case CONST_INT: |
| case CONST_DOUBLE: |
| case MEM: |
| hi = operand_subword (op, TARGET_LITTLE_ENDIAN, 0, mode); |
| lo = operand_subword (op, TARGET_BIG_ENDIAN, 0, mode); |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| /* The high part of CR <- GPR moves must be done after the low part. */ |
| operands [i + 4] = lo; |
| operands [i + 2] = hi; |
| } |
| |
| if (reg_mentioned_p (operands[2], operands[5]) |
| || GET_CODE (operands[2]) == ZERO_EXTRACT |
| || GET_CODE (operands[4]) == ZERO_EXTRACT) |
| { |
| rtx tmp; |
| |
| /* Overlapping register pairs -- make sure we don't |
| early-clobber ourselves. */ |
| tmp = operands[2]; |
| operands[2] = operands[4]; |
| operands[4] = tmp; |
| tmp = operands[3]; |
| operands[3] = operands[5]; |
| operands[5] = tmp; |
| } |
| |
| #if DEBUG_SPLIT_WIDE_MOVE |
| fprintf(stderr, "\033[34m"); |
| debug_rtx (operands[2]); |
| debug_rtx (operands[3]); |
| debug_rtx (operands[4]); |
| debug_rtx (operands[5]); |
| fprintf(stderr, "\033[0m"); |
| #endif |
| } |
| |
| /* Emit a setcc instruction in its entirity. */ |
| |
| static bool |
| mep_expand_setcc_1 (enum rtx_code code, rtx dest, rtx op1, rtx op2) |
| { |
| rtx tmp; |
| |
| switch (code) |
| { |
| case GT: |
| case GTU: |
| tmp = op1, op1 = op2, op2 = tmp; |
| code = swap_condition (code); |
| /* FALLTHRU */ |
| |
| case LT: |
| case LTU: |
| op1 = force_reg (SImode, op1); |
| emit_insn (gen_rtx_SET (VOIDmode, dest, |
| gen_rtx_fmt_ee (code, SImode, op1, op2))); |
| return true; |
| |
| case EQ: |
| if (op2 != const0_rtx) |
| op1 = expand_binop (SImode, sub_optab, op1, op2, NULL, 1, OPTAB_WIDEN); |
| mep_expand_setcc_1 (LTU, dest, op1, const1_rtx); |
| return true; |
| |
| case NE: |
| /* Branchful sequence: |
| mov dest, 0 16-bit |
| beq op1, op2, Lover 16-bit (op2 < 16), 32-bit otherwise |
| mov dest, 1 16-bit |
| |
| Branchless sequence: |
| add3 tmp, op1, -op2 32-bit (or mov + sub) |
| sltu3 tmp, tmp, 1 16-bit |
| xor3 dest, tmp, 1 32-bit |
| */ |
| if (optimize_size && op2 != const0_rtx) |
| return false; |
| |
| if (op2 != const0_rtx) |
| op1 = expand_binop (SImode, sub_optab, op1, op2, NULL, 1, OPTAB_WIDEN); |
| |
| op2 = gen_reg_rtx (SImode); |
| mep_expand_setcc_1 (LTU, op2, op1, const1_rtx); |
| |
| emit_insn (gen_rtx_SET (VOIDmode, dest, |
| gen_rtx_XOR (SImode, op2, const1_rtx))); |
| return true; |
| |
| case LE: |
| if (GET_CODE (op2) != CONST_INT |
| || INTVAL (op2) == 0x7ffffff) |
| return false; |
| op2 = GEN_INT (INTVAL (op2) + 1); |
| return mep_expand_setcc_1 (LT, dest, op1, op2); |
| |
| case LEU: |
| if (GET_CODE (op2) != CONST_INT |
| || INTVAL (op2) == -1) |
| return false; |
| op2 = GEN_INT (trunc_int_for_mode (INTVAL (op2) + 1, SImode)); |
| return mep_expand_setcc_1 (LTU, dest, op1, op2); |
| |
| case GE: |
| if (GET_CODE (op2) != CONST_INT |
| || INTVAL (op2) == trunc_int_for_mode (0x80000000, SImode)) |
| return false; |
| op2 = GEN_INT (INTVAL (op2) - 1); |
| return mep_expand_setcc_1 (GT, dest, op1, op2); |
| |
| case GEU: |
| if (GET_CODE (op2) != CONST_INT |
| || op2 == const0_rtx) |
| return false; |
| op2 = GEN_INT (trunc_int_for_mode (INTVAL (op2) - 1, SImode)); |
| return mep_expand_setcc_1 (GTU, dest, op1, op2); |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| bool |
| mep_expand_setcc (rtx *operands) |
| { |
| rtx dest = operands[0]; |
| enum rtx_code code = GET_CODE (operands[1]); |
| rtx op0 = operands[2]; |
| rtx op1 = operands[3]; |
| |
| return mep_expand_setcc_1 (code, dest, op0, op1); |
| } |
| |
| rtx |
| mep_expand_cbranch (rtx *operands) |
| { |
| enum rtx_code code = GET_CODE (operands[0]); |
| rtx op0 = operands[1]; |
| rtx op1 = operands[2]; |
| rtx tmp; |
| |
| restart: |
| switch (code) |
| { |
| case LT: |
| if (mep_imm4_operand (op1, SImode)) |
| break; |
| |
| tmp = gen_reg_rtx (SImode); |
| gcc_assert (mep_expand_setcc_1 (LT, tmp, op0, op1)); |
| code = NE; |
| op0 = tmp; |
| op1 = const0_rtx; |
| break; |
| |
| case GE: |
| if (mep_imm4_operand (op1, SImode)) |
| break; |
| |
| tmp = gen_reg_rtx (SImode); |
| gcc_assert (mep_expand_setcc_1 (LT, tmp, op0, op1)); |
| |
| code = EQ; |
| op0 = tmp; |
| op1 = const0_rtx; |
| break; |
| |
| case EQ: |
| case NE: |
| if (! mep_reg_or_imm4_operand (op1, SImode)) |
| op1 = force_reg (SImode, op1); |
| break; |
| |
| case LE: |
| case GT: |
| if (GET_CODE (op1) == CONST_INT |
| && INTVAL (op1) != 0x7fffffff) |
| { |
| op1 = GEN_INT (INTVAL (op1) + 1); |
| code = (code == LE ? LT : GE); |
| goto restart; |
| } |
| |
| tmp = gen_reg_rtx (SImode); |
| gcc_assert (mep_expand_setcc_1 (LT, tmp, op1, op0)); |
| |
| code = (code == LE ? EQ : NE); |
| op0 = tmp; |
| op1 = const0_rtx; |
| break; |
| |
| case LTU: |
| if (op1 == const1_rtx) |
| { |
| code = EQ; |
| op1 = const0_rtx; |
| break; |
| } |
| |
| tmp = gen_reg_rtx (SImode); |
| gcc_assert (mep_expand_setcc_1 (LTU, tmp, op0, op1)); |
| code = NE; |
| op0 = tmp; |
| op1 = const0_rtx; |
| break; |
| |
| case LEU: |
| tmp = gen_reg_rtx (SImode); |
| if (mep_expand_setcc_1 (LEU, tmp, op0, op1)) |
| code = NE; |
| else if (mep_expand_setcc_1 (LTU, tmp, op1, op0)) |
| code = EQ; |
| else |
| gcc_unreachable (); |
| op0 = tmp; |
| op1 = const0_rtx; |
| break; |
| |
| case GTU: |
| tmp = gen_reg_rtx (SImode); |
| gcc_assert (mep_expand_setcc_1 (GTU, tmp, op0, op1) |
| || mep_expand_setcc_1 (LTU, tmp, op1, op0)); |
| code = NE; |
| op0 = tmp; |
| op1 = const0_rtx; |
| break; |
| |
| case GEU: |
| tmp = gen_reg_rtx (SImode); |
| if (mep_expand_setcc_1 (GEU, tmp, op0, op1)) |
| code = NE; |
| else if (mep_expand_setcc_1 (LTU, tmp, op0, op1)) |
| code = EQ; |
| else |
| gcc_unreachable (); |
| op0 = tmp; |
| op1 = const0_rtx; |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| return gen_rtx_fmt_ee (code, VOIDmode, op0, op1); |
| } |
| |
| const char * |
| mep_emit_cbranch (rtx *operands, int ne) |
| { |
| if (GET_CODE (operands[1]) == REG) |
| return ne ? "bne\t%0, %1, %l2" : "beq\t%0, %1, %l2"; |
| else if (INTVAL (operands[1]) == 0 && !mep_vliw_function_p(cfun->decl)) |
| return ne ? "bnez\t%0, %l2" : "beqz\t%0, %l2"; |
| else |
| return ne ? "bnei\t%0, %1, %l2" : "beqi\t%0, %1, %l2"; |
| } |
| |
| void |
| mep_expand_call (rtx *operands, int returns_value) |
| { |
| rtx addr = operands[returns_value]; |
| rtx tp = mep_tp_rtx (); |
| rtx gp = mep_gp_rtx (); |
| |
| gcc_assert (GET_CODE (addr) == MEM); |
| |
| addr = XEXP (addr, 0); |
| |
| if (! mep_call_address_operand (addr, VOIDmode)) |
| addr = force_reg (SImode, addr); |
| |
| if (! operands[returns_value+2]) |
| operands[returns_value+2] = const0_rtx; |
| |
| if (returns_value) |
| emit_call_insn (gen_call_value_internal (operands[0], addr, operands[2], |
| operands[3], tp, gp)); |
| else |
| emit_call_insn (gen_call_internal (addr, operands[1], |
| operands[2], tp, gp)); |
| } |
| |
| /* Aliasing Support. */ |
| |
| /* If X is a machine specific address (i.e. a symbol or label being |
| referenced as a displacement from the GOT implemented using an |
| UNSPEC), then return the base term. Otherwise return X. */ |
| |
| rtx |
| mep_find_base_term (rtx x) |
| { |
| rtx base, term; |
| int unspec; |
| |
| if (GET_CODE (x) != PLUS) |
| return x; |
| base = XEXP (x, 0); |
| term = XEXP (x, 1); |
| |
| if (has_hard_reg_initial_val(Pmode, TP_REGNO) |
| && base == mep_tp_rtx ()) |
| unspec = UNS_TPREL; |
| else if (has_hard_reg_initial_val(Pmode, GP_REGNO) |
| && base == mep_gp_rtx ()) |
| unspec = UNS_GPREL; |
| else |
| return x; |
| |
| if (GET_CODE (term) != CONST) |
| return x; |
| term = XEXP (term, 0); |
| |
| if (GET_CODE (term) != UNSPEC |
| || XINT (term, 1) != unspec) |
| return x; |
| |
| return XVECEXP (term, 0, 0); |
| } |
| |
| /* Reload Support. */ |
| |
| /* Return true if the registers in CLASS cannot represent the change from |
| modes FROM to TO. */ |
| |
| bool |
| mep_cannot_change_mode_class (enum machine_mode from, enum machine_mode to, |
| enum reg_class regclass) |
| { |
| if (from == to) |
| return false; |
| |
| /* 64-bit COP regs must remain 64-bit COP regs. */ |
| if (TARGET_64BIT_CR_REGS |
| && (regclass == CR_REGS |
| || regclass == LOADABLE_CR_REGS) |
| && (GET_MODE_SIZE (to) < 8 |
| || GET_MODE_SIZE (from) < 8)) |
| return true; |
| |
| return false; |
| } |
| |
| #define MEP_NONGENERAL_CLASS(C) (!reg_class_subset_p (C, GENERAL_REGS)) |
| |
| static bool |
| mep_general_reg (rtx x) |
| { |
| while (GET_CODE (x) == SUBREG) |
| x = XEXP (x, 0); |
| return GET_CODE (x) == REG && GR_REGNO_P (REGNO (x)); |
| } |
| |
| static bool |
| mep_nongeneral_reg (rtx x) |
| { |
| while (GET_CODE (x) == SUBREG) |
| x = XEXP (x, 0); |
| return (GET_CODE (x) == REG |
| && !GR_REGNO_P (REGNO (x)) && REGNO (x) < FIRST_PSEUDO_REGISTER); |
| } |
| |
| static bool |
| mep_general_copro_reg (rtx x) |
| { |
| while (GET_CODE (x) == SUBREG) |
| x = XEXP (x, 0); |
| return (GET_CODE (x) == REG && CR_REGNO_P (REGNO (x))); |
| } |
| |
| static bool |
| mep_nonregister (rtx x) |
| { |
| while (GET_CODE (x) == SUBREG) |
| x = XEXP (x, 0); |
| return (GET_CODE (x) != REG || REGNO (x) >= FIRST_PSEUDO_REGISTER); |
| } |
| |
| #define DEBUG_RELOAD 0 |
| |
| /* Return the secondary reload class needed for moving value X to or |
| from a register in coprocessor register class CLASS. */ |
| |
| static enum reg_class |
| mep_secondary_copro_reload_class (enum reg_class rclass, rtx x) |
| { |
| if (mep_general_reg (x)) |
| /* We can do the move directly if mep_have_core_copro_moves_p, |
| otherwise we need to go through memory. Either way, no secondary |
| register is needed. */ |
| return NO_REGS; |
| |
| if (mep_general_copro_reg (x)) |
| { |
| /* We can do the move directly if mep_have_copro_copro_moves_p. */ |
| if (mep_have_copro_copro_moves_p) |
| return NO_REGS; |
| |
| /* Otherwise we can use a temporary if mep_have_core_copro_moves_p. */ |
| if (mep_have_core_copro_moves_p) |
| return GENERAL_REGS; |
| |
| /* Otherwise we need to do it through memory. No secondary |
| register is needed. */ |
| return NO_REGS; |
| } |
| |
| if (reg_class_subset_p (rclass, LOADABLE_CR_REGS) |
| && constraint_satisfied_p (x, CONSTRAINT_U)) |
| /* X is a memory value that we can access directly. */ |
| return NO_REGS; |
| |
| /* We have to move X into a GPR first and then copy it to |
| the coprocessor register. The move from the GPR to the |
| coprocessor might be done directly or through memory, |
| depending on mep_have_core_copro_moves_p. */ |
| return GENERAL_REGS; |
| } |
| |
| /* Copying X to register in RCLASS. */ |
| |
| enum reg_class |
| mep_secondary_input_reload_class (enum reg_class rclass, |
| enum machine_mode mode ATTRIBUTE_UNUSED, |
| rtx x) |
| { |
| int rv = NO_REGS; |
| |
| #if DEBUG_RELOAD |
| fprintf (stderr, "secondary input reload copy to %s %s from ", reg_class_names[rclass], mode_name[mode]); |
| debug_rtx (x); |
| #endif |
| |
| if (reg_class_subset_p (rclass, CR_REGS)) |
| rv = mep_secondary_copro_reload_class (rclass, x); |
| else if (MEP_NONGENERAL_CLASS (rclass) |
| && (mep_nonregister (x) || mep_nongeneral_reg (x))) |
| rv = GENERAL_REGS; |
| |
| #if DEBUG_RELOAD |
| fprintf (stderr, " - requires %s\n", reg_class_names[rv]); |
| #endif |
| return (enum reg_class) rv; |
| } |
| |
| /* Copying register in RCLASS to X. */ |
| |
| enum reg_class |
| mep_secondary_output_reload_class (enum reg_class rclass, |
| enum machine_mode mode ATTRIBUTE_UNUSED, |
| rtx x) |
| { |
| int rv = NO_REGS; |
| |
| #if DEBUG_RELOAD |
| fprintf (stderr, "secondary output reload copy from %s %s to ", reg_class_names[rclass], mode_name[mode]); |
| debug_rtx (x); |
| #endif |
| |
| if (reg_class_subset_p (rclass, CR_REGS)) |
| rv = mep_secondary_copro_reload_class (rclass, x); |
| else if (MEP_NONGENERAL_CLASS (rclass) |
| && (mep_nonregister (x) || mep_nongeneral_reg (x))) |
| rv = GENERAL_REGS; |
| |
| #if DEBUG_RELOAD |
| fprintf (stderr, " - requires %s\n", reg_class_names[rv]); |
| #endif |
| |
| return (enum reg_class) rv; |
| } |
| |
| /* Implement SECONDARY_MEMORY_NEEDED. */ |
| |
| bool |
| mep_secondary_memory_needed (enum reg_class rclass1, enum reg_class rclass2, |
| enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| if (!mep_have_core_copro_moves_p) |
| { |
| if (reg_classes_intersect_p (rclass1, CR_REGS) |
| && reg_classes_intersect_p (rclass2, GENERAL_REGS)) |
| return true; |
| if (reg_classes_intersect_p (rclass2, CR_REGS) |
| && reg_classes_intersect_p (rclass1, GENERAL_REGS)) |
| return true; |
| if (!mep_have_copro_copro_moves_p |
| && reg_classes_intersect_p (rclass1, CR_REGS) |
| && reg_classes_intersect_p (rclass2, CR_REGS)) |
| return true; |
| } |
| return false; |
| } |
| |
| void |
| mep_expand_reload (rtx *operands, enum machine_mode mode) |
| { |
| /* There are three cases for each direction: |
| register, farsym |
| control, farsym |
| control, nearsym */ |
| |
| int s0 = mep_section_tag (operands[0]) == 'f'; |
| int s1 = mep_section_tag (operands[1]) == 'f'; |
| int c0 = mep_nongeneral_reg (operands[0]); |
| int c1 = mep_nongeneral_reg (operands[1]); |
| int which = (s0 ? 20:0) + (c0 ? 10:0) + (s1 ? 2:0) + (c1 ? 1:0); |
| |
| #if DEBUG_RELOAD |
| fprintf (stderr, "expand_reload %s\n", mode_name[mode]); |
| debug_rtx (operands[0]); |
| debug_rtx (operands[1]); |
| #endif |
| |
| switch (which) |
| { |
| case 00: /* Don't know why this gets here. */ |
| case 02: /* general = far */ |
| emit_move_insn (operands[0], operands[1]); |
| return; |
| |
| case 10: /* cr = mem */ |
| case 11: /* cr = cr */ |
| case 01: /* mem = cr */ |
| case 12: /* cr = far */ |
| emit_move_insn (operands[2], operands[1]); |
| emit_move_insn (operands[0], operands[2]); |
| return; |
| |
| case 20: /* far = general */ |
| emit_move_insn (operands[2], XEXP (operands[1], 0)); |
| emit_move_insn (operands[0], gen_rtx_MEM (mode, operands[2])); |
| return; |
| |
| case 21: /* far = cr */ |
| case 22: /* far = far */ |
| default: |
| fprintf (stderr, "unsupported expand reload case %02d for mode %s\n", |
| which, mode_name[mode]); |
| debug_rtx (operands[0]); |
| debug_rtx (operands[1]); |
| gcc_unreachable (); |
| } |
| } |
| |
| /* Implement PREFERRED_RELOAD_CLASS. See whether X is a constant that |
| can be moved directly into registers 0 to 7, but not into the rest. |
| If so, and if the required class includes registers 0 to 7, restrict |
| it to those registers. */ |
| |
| enum reg_class |
| mep_preferred_reload_class (rtx x, enum reg_class rclass) |
| { |
| switch (GET_CODE (x)) |
| { |
| case CONST_INT: |
| if (INTVAL (x) >= 0x10000 |
| && INTVAL (x) < 0x01000000 |
| && (INTVAL (x) & 0xffff) != 0 |
| && reg_class_subset_p (TPREL_REGS, rclass)) |
| rclass = TPREL_REGS; |
| break; |
| |
| case CONST: |
| case SYMBOL_REF: |
| case LABEL_REF: |
| if (mep_section_tag (x) != 'f' |
| && reg_class_subset_p (TPREL_REGS, rclass)) |
| rclass = TPREL_REGS; |
| break; |
| |
| default: |
| break; |
| } |
| return rclass; |
| } |
| |
| /* Implement REGISTER_MOVE_COST. Return 2 for direct single-register |
| moves, 4 for direct double-register moves, and 1000 for anything |
| that requires a temporary register or temporary stack slot. */ |
| |
| int |
| mep_register_move_cost (enum machine_mode mode, enum reg_class from, enum reg_class to) |
| { |
| if (mep_have_copro_copro_moves_p |
| && reg_class_subset_p (from, CR_REGS) |
| && reg_class_subset_p (to, CR_REGS)) |
| { |
| if (TARGET_32BIT_CR_REGS && GET_MODE_SIZE (mode) > UNITS_PER_WORD) |
| return 4; |
| return 2; |
| } |
| if (reg_class_subset_p (from, CR_REGS) |
| && reg_class_subset_p (to, CR_REGS)) |
| { |
| if (TARGET_32BIT_CR_REGS && GET_MODE_SIZE (mode) > UNITS_PER_WORD) |
| return 8; |
| return 4; |
| } |
| if (reg_class_subset_p (from, CR_REGS) |
| || reg_class_subset_p (to, CR_REGS)) |
| { |
| if (GET_MODE_SIZE (mode) > UNITS_PER_WORD) |
| return 4; |
| return 2; |
| } |
| if (mep_secondary_memory_needed (from, to, mode)) |
| return 1000; |
| if (MEP_NONGENERAL_CLASS (from) && MEP_NONGENERAL_CLASS (to)) |
| return 1000; |
| |
| if (GET_MODE_SIZE (mode) > 4) |
| return 4; |
| |
| return 2; |
| } |
| |
| |
| /* Functions to save and restore machine-specific function data. */ |
| |
| static struct machine_function * |
| mep_init_machine_status (void) |
| { |
| return ggc_alloc_cleared_machine_function (); |
| } |
| |
| static rtx |
| mep_allocate_initial_value (rtx reg) |
| { |
| int rss; |
| |
| if (GET_CODE (reg) != REG) |
| return NULL_RTX; |
| |
| if (REGNO (reg) >= FIRST_PSEUDO_REGISTER) |
| return NULL_RTX; |
| |
| /* In interrupt functions, the "initial" values of $gp and $tp are |
| provided by the prologue. They are not necessarily the same as |
| the values that the caller was using. */ |
| if (REGNO (reg) == TP_REGNO || REGNO (reg) == GP_REGNO) |
| if (mep_interrupt_p ()) |
| return NULL_RTX; |
| |
| if (! cfun->machine->reg_save_slot[REGNO(reg)]) |
| { |
| cfun->machine->reg_save_size += 4; |
| cfun->machine->reg_save_slot[REGNO(reg)] = cfun->machine->reg_save_size; |
| } |
| |
| rss = cfun->machine->reg_save_slot[REGNO(reg)]; |
| return gen_rtx_MEM (SImode, plus_constant (Pmode, arg_pointer_rtx, -rss)); |
| } |
| |
| rtx |
| mep_return_addr_rtx (int count) |
| { |
| if (count != 0) |
| return const0_rtx; |
| |
| return get_hard_reg_initial_val (Pmode, LP_REGNO); |
| } |
| |
| static rtx |
| mep_tp_rtx (void) |
| { |
| return get_hard_reg_initial_val (Pmode, TP_REGNO); |
| } |
| |
| static rtx |
| mep_gp_rtx (void) |
| { |
| return get_hard_reg_initial_val (Pmode, GP_REGNO); |
| } |
| |
| static bool |
| mep_interrupt_p (void) |
| { |
| if (cfun->machine->interrupt_handler == 0) |
| { |
| int interrupt_handler |
| = (lookup_attribute ("interrupt", |
| DECL_ATTRIBUTES (current_function_decl)) |
| != NULL_TREE); |
| cfun->machine->interrupt_handler = interrupt_handler ? 2 : 1; |
| } |
| return cfun->machine->interrupt_handler == 2; |
| } |
| |
| static bool |
| mep_disinterrupt_p (void) |
| { |
| if (cfun->machine->disable_interrupts == 0) |
| { |
| int disable_interrupts |
| = (lookup_attribute ("disinterrupt", |
| DECL_ATTRIBUTES (current_function_decl)) |
| != NULL_TREE); |
| cfun->machine->disable_interrupts = disable_interrupts ? 2 : 1; |
| } |
| return cfun->machine->disable_interrupts == 2; |
| } |
| |
| |
| /* Frame/Epilog/Prolog Related. */ |
| |
| static bool |
| mep_reg_set_p (rtx reg, rtx insn) |
| { |
| /* Similar to reg_set_p in rtlanal.c, but we ignore calls */ |
| if (INSN_P (insn)) |
| { |
| if (FIND_REG_INC_NOTE (insn, reg)) |
| return true; |
| insn = PATTERN (insn); |
| } |
| |
| if (GET_CODE (insn) == SET |
| && GET_CODE (XEXP (insn, 0)) == REG |
| && GET_CODE (XEXP (insn, 1)) == REG |
| && REGNO (XEXP (insn, 0)) == REGNO (XEXP (insn, 1))) |
| return false; |
| |
| return set_of (reg, insn) != NULL_RTX; |
| } |
| |
| |
| #define MEP_SAVES_UNKNOWN 0 |
| #define MEP_SAVES_YES 1 |
| #define MEP_SAVES_MAYBE 2 |
| #define MEP_SAVES_NO 3 |
| |
| static bool |
| mep_reg_set_in_function (int regno) |
| { |
| rtx reg, insn; |
| |
| if (mep_interrupt_p () && df_regs_ever_live_p(regno)) |
| return true; |
| |
| if (regno == LP_REGNO && (profile_arc_flag > 0 || profile_flag > 0)) |
| return true; |
| |
| push_topmost_sequence (); |
| insn = get_insns (); |
| pop_topmost_sequence (); |
| |
| if (!insn) |
| return false; |
| |
| reg = gen_rtx_REG (SImode, regno); |
| |
| for (insn = NEXT_INSN (insn); insn; insn = NEXT_INSN (insn)) |
| if (INSN_P (insn) && mep_reg_set_p (reg, insn)) |
| return true; |
| return false; |
| } |
| |
| static bool |
| mep_asm_without_operands_p (void) |
| { |
| if (cfun->machine->asms_without_operands == 0) |
| { |
| rtx insn; |
| |
| push_topmost_sequence (); |
| insn = get_insns (); |
| pop_topmost_sequence (); |
| |
| cfun->machine->asms_without_operands = 1; |
| while (insn) |
| { |
| if (INSN_P (insn) |
| && GET_CODE (PATTERN (insn)) == ASM_INPUT) |
| { |
| cfun->machine->asms_without_operands = 2; |
| break; |
| } |
| insn = NEXT_INSN (insn); |
| } |
| |
| } |
| return cfun->machine->asms_without_operands == 2; |
| } |
| |
| /* Interrupt functions save/restore every call-preserved register, and |
| any call-used register it uses (or all if it calls any function, |
| since they may get clobbered there too). Here we check to see |
| which call-used registers need saving. */ |
| |
| #define IVC2_ISAVED_REG(r) (TARGET_IVC2 \ |
| && (r == FIRST_CCR_REGNO + 1 \ |
| || (r >= FIRST_CCR_REGNO + 8 && r <= FIRST_CCR_REGNO + 11) \ |
| || (r >= FIRST_CCR_REGNO + 16 && r <= FIRST_CCR_REGNO + 31))) |
| |
| static bool |
| mep_interrupt_saved_reg (int r) |
| { |
| if (!mep_interrupt_p ()) |
| return false; |
| if (r == REGSAVE_CONTROL_TEMP |
| || (TARGET_64BIT_CR_REGS && TARGET_COP && r == REGSAVE_CONTROL_TEMP+1)) |
| return true; |
| if (mep_asm_without_operands_p () |
| && (!fixed_regs[r] |
| || (r == RPB_REGNO || r == RPE_REGNO || r == RPC_REGNO || r == LP_REGNO) |
| || IVC2_ISAVED_REG (r))) |
| return true; |
| if (!crtl->is_leaf) |
| /* Function calls mean we need to save $lp. */ |
| if (r == LP_REGNO || IVC2_ISAVED_REG (r)) |
| return true; |
| if (!crtl->is_leaf || cfun->machine->doloop_tags > 0) |
| /* The interrupt handler might use these registers for repeat blocks, |
| or it might call a function that does so. */ |
| if (r == RPB_REGNO || r == RPE_REGNO || r == RPC_REGNO) |
| return true; |
| if (crtl->is_leaf && call_used_regs[r] && !df_regs_ever_live_p(r)) |
| return false; |
| /* Functions we call might clobber these. */ |
| if (call_used_regs[r] && !fixed_regs[r]) |
| return true; |
| /* Additional registers that need to be saved for IVC2. */ |
| if (IVC2_ISAVED_REG (r)) |
| return true; |
| |
| return false; |
| } |
| |
| static bool |
| mep_call_saves_register (int r) |
| { |
| if (! cfun->machine->frame_locked) |
| { |
| int rv = MEP_SAVES_NO; |
| |
| if (cfun->machine->reg_save_slot[r]) |
| rv = MEP_SAVES_YES; |
| else if (r == LP_REGNO && (profile_arc_flag > 0 || profile_flag > 0)) |
| rv = MEP_SAVES_YES; |
| else if (r == FRAME_POINTER_REGNUM && frame_pointer_needed) |
| rv = MEP_SAVES_YES; |
| else if ((!call_used_regs[r] || r == LP_REGNO) && df_regs_ever_live_p(r)) |
| rv = MEP_SAVES_YES; |
| else if (crtl->calls_eh_return && (r == 10 || r == 11)) |
| /* We need these to have stack slots so that they can be set during |
| unwinding. */ |
| rv = MEP_SAVES_YES; |
| else if (mep_interrupt_saved_reg (r)) |
| rv = MEP_SAVES_YES; |
| cfun->machine->reg_saved[r] = rv; |
| } |
| return cfun->machine->reg_saved[r] == MEP_SAVES_YES; |
| } |
| |
| /* Return true if epilogue uses register REGNO. */ |
| |
| bool |
| mep_epilogue_uses (int regno) |
| { |
| /* Since $lp is a call-saved register, the generic code will normally |
| mark it used in the epilogue if it needs to be saved and restored. |
| However, when profiling is enabled, the profiling code will implicitly |
| clobber $11. This case has to be handled specially both here and in |
| mep_call_saves_register. */ |
| if (regno == LP_REGNO && (profile_arc_flag > 0 || profile_flag > 0)) |
| return true; |
| /* Interrupt functions save/restore pretty much everything. */ |
| return (reload_completed && mep_interrupt_saved_reg (regno)); |
| } |
| |
| static int |
| mep_reg_size (int regno) |
| { |
| if (CR_REGNO_P (regno) && TARGET_64BIT_CR_REGS) |
| return 8; |
| return 4; |
| } |
| |
| /* Worker function for TARGET_CAN_ELIMINATE. */ |
| |
| bool |
| mep_can_eliminate (const int from, const int to) |
| { |
| return (from == ARG_POINTER_REGNUM && to == STACK_POINTER_REGNUM |
| ? ! frame_pointer_needed |
| : true); |
| } |
| |
| int |
| mep_elimination_offset (int from, int to) |
| { |
| int reg_save_size; |
| int i; |
| int frame_size = get_frame_size () + crtl->outgoing_args_size; |
| int total_size; |
| |
| if (!cfun->machine->frame_locked) |
| memset (cfun->machine->reg_saved, 0, sizeof (cfun->machine->reg_saved)); |
| |
| /* We don't count arg_regs_to_save in the arg pointer offset, because |
| gcc thinks the arg pointer has moved along with the saved regs. |
| However, we do count it when we adjust $sp in the prologue. */ |
| reg_save_size = 0; |
| for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) |
| if (mep_call_saves_register (i)) |
| reg_save_size += mep_reg_size (i); |
| |
| if (reg_save_size % 8) |
| cfun->machine->regsave_filler = 8 - (reg_save_size % 8); |
| else |
| cfun->machine->regsave_filler = 0; |
| |
| /* This is what our total stack adjustment looks like. */ |
| total_size = (reg_save_size + frame_size + cfun->machine->regsave_filler); |
| |
| if (total_size % 8) |
| cfun->machine->frame_filler = 8 - (total_size % 8); |
| else |
| cfun->machine->frame_filler = 0; |
| |
| |
| if (from == ARG_POINTER_REGNUM && to == FRAME_POINTER_REGNUM) |
| return reg_save_size + cfun->machine->regsave_filler; |
| |
| if (from == FRAME_POINTER_REGNUM && to == STACK_POINTER_REGNUM) |
| return cfun->machine->frame_filler + frame_size; |
| |
| if (from == ARG_POINTER_REGNUM && to == STACK_POINTER_REGNUM) |
| return reg_save_size + cfun->machine->regsave_filler + cfun->machine->frame_filler + frame_size; |
| |
| gcc_unreachable (); |
| } |
| |
| static rtx |
| F (rtx x) |
| { |
| RTX_FRAME_RELATED_P (x) = 1; |
| return x; |
| } |
| |
| /* Since the prologue/epilogue code is generated after optimization, |
| we can't rely on gcc to split constants for us. So, this code |
| captures all the ways to add a constant to a register in one logic |
| chunk, including optimizing away insns we just don't need. This |
| makes the prolog/epilog code easier to follow. */ |
| static void |
| add_constant (int dest, int src, int value, int mark_frame) |
| { |
| rtx insn; |
| int hi, lo; |
| |
| if (src == dest && value == 0) |
| return; |
| |
| if (value == 0) |
| { |
| insn = emit_move_insn (gen_rtx_REG (SImode, dest), |
| gen_rtx_REG (SImode, src)); |
| if (mark_frame) |
| RTX_FRAME_RELATED_P(insn) = 1; |
| return; |
| } |
| |
| if (value >= -32768 && value <= 32767) |
| { |
| insn = emit_insn (gen_addsi3 (gen_rtx_REG (SImode, dest), |
| gen_rtx_REG (SImode, src), |
| GEN_INT (value))); |
| if (mark_frame) |
| RTX_FRAME_RELATED_P(insn) = 1; |
| return; |
| } |
| |
| /* Big constant, need to use a temp register. We use |
| REGSAVE_CONTROL_TEMP because it's call clobberable (the reg save |
| area is always small enough to directly add to). */ |
| |
| hi = trunc_int_for_mode (value & 0xffff0000, SImode); |
| lo = value & 0xffff; |
| |
| insn = emit_move_insn (gen_rtx_REG (SImode, REGSAVE_CONTROL_TEMP), |
| GEN_INT (hi)); |
| |
| if (lo) |
| { |
| insn = emit_insn (gen_iorsi3 (gen_rtx_REG (SImode, REGSAVE_CONTROL_TEMP), |
| gen_rtx_REG (SImode, REGSAVE_CONTROL_TEMP), |
| GEN_INT (lo))); |
| } |
| |
| insn = emit_insn (gen_addsi3 (gen_rtx_REG (SImode, dest), |
| gen_rtx_REG (SImode, src), |
| gen_rtx_REG (SImode, REGSAVE_CONTROL_TEMP))); |
| if (mark_frame) |
| { |
| RTX_FRAME_RELATED_P(insn) = 1; |
| add_reg_note (insn, REG_FRAME_RELATED_EXPR, |
| gen_rtx_SET (SImode, |
| gen_rtx_REG (SImode, dest), |
| gen_rtx_PLUS (SImode, |
| gen_rtx_REG (SImode, dest), |
| GEN_INT (value)))); |
| } |
| } |
| |
| /* Move SRC to DEST. Mark the move as being potentially dead if |
| MAYBE_DEAD_P. */ |
| |
| static rtx |
| maybe_dead_move (rtx dest, rtx src, bool ATTRIBUTE_UNUSED maybe_dead_p) |
| { |
| rtx insn = emit_move_insn (dest, src); |
| #if 0 |
| if (maybe_dead_p) |
| REG_NOTES (insn) = gen_rtx_EXPR_LIST (REG_MAYBE_DEAD, const0_rtx, NULL); |
| #endif |
| return insn; |
| } |
| |
| /* Used for interrupt functions, which can't assume that $tp and $gp |
| contain the correct pointers. */ |
| |
| static void |
| mep_reload_pointer (int regno, const char *symbol) |
| { |
| rtx reg, sym; |
| |
| if (!df_regs_ever_live_p(regno) && crtl->is_leaf) |
| return; |
| |
| reg = gen_rtx_REG (SImode, regno); |
| sym = gen_rtx_SYMBOL_REF (SImode, symbol); |
| emit_insn (gen_movsi_topsym_s (reg, sym)); |
| emit_insn (gen_movsi_botsym_s (reg, reg, sym)); |
| } |
| |
| /* Assign save slots for any register not already saved. DImode |
| registers go at the end of the reg save area; the rest go at the |
| beginning. This is for alignment purposes. Returns true if a frame |
| is really needed. */ |
| static bool |
| mep_assign_save_slots (int reg_save_size) |
| { |
| bool really_need_stack_frame = false; |
| int di_ofs = 0; |
| int i; |
| |
| for (i=0; i<FIRST_PSEUDO_REGISTER; i++) |
| if (mep_call_saves_register(i)) |
| { |
| int regsize = mep_reg_size (i); |
| |
| if ((i != TP_REGNO && i != GP_REGNO && i != LP_REGNO) |
| || mep_reg_set_in_function (i)) |
| really_need_stack_frame = true; |
| |
| if (cfun->machine->reg_save_slot[i]) |
| continue; |
| |
| if (regsize < 8) |
| { |
| cfun->machine->reg_save_size += regsize; |
| cfun->machine->reg_save_slot[i] = cfun->machine->reg_save_size; |
| } |
| else |
| { |
| cfun->machine->reg_save_slot[i] = reg_save_size - di_ofs; |
| di_ofs += 8; |
| } |
| } |
| cfun->machine->frame_locked = 1; |
| return really_need_stack_frame; |
| } |
| |
| void |
| mep_expand_prologue (void) |
| { |
| int i, rss, sp_offset = 0; |
| int reg_save_size; |
| int frame_size; |
| int really_need_stack_frame; |
| |
| /* We must not allow register renaming in interrupt functions, |
| because that invalidates the correctness of the set of call-used |
| registers we're going to save/restore. */ |
| mep_set_leaf_registers (mep_interrupt_p () ? 0 : 1); |
| |
| if (mep_disinterrupt_p ()) |
| emit_insn (gen_mep_disable_int ()); |
| |
| cfun->machine->mep_frame_pointer_needed = frame_pointer_needed; |
| |
| reg_save_size = mep_elimination_offset (ARG_POINTER_REGNUM, FRAME_POINTER_REGNUM); |
| frame_size = mep_elimination_offset (FRAME_POINTER_REGNUM, STACK_POINTER_REGNUM); |
| really_need_stack_frame = frame_size; |
| |
| really_need_stack_frame |= mep_assign_save_slots (reg_save_size); |
| |
| sp_offset = reg_save_size; |
| if (sp_offset + frame_size < 128) |
| sp_offset += frame_size ; |
| |
| add_constant (SP_REGNO, SP_REGNO, -sp_offset, 1); |
| |
| for (i=0; i<FIRST_PSEUDO_REGISTER; i++) |
| if (mep_call_saves_register(i)) |
| { |
| rtx mem; |
| bool maybe_dead_p; |
| enum machine_mode rmode; |
| |
| rss = cfun->machine->reg_save_slot[i]; |
| |
| if ((i == TP_REGNO || i == GP_REGNO || i == LP_REGNO) |
| && (!mep_reg_set_in_function (i) |
| && !mep_interrupt_p ())) |
| continue; |
| |
| if (mep_reg_size (i) == 8) |
| rmode = DImode; |
| else |
| rmode = SImode; |
| |
| /* If there is a pseudo associated with this register's initial value, |
| reload might have already spilt it to the stack slot suggested by |
| ALLOCATE_INITIAL_VALUE. The moves emitted here can then be safely |
| deleted as dead. */ |
| mem = gen_rtx_MEM (rmode, |
| plus_constant (Pmode, stack_pointer_rtx, |
| sp_offset - rss)); |
| maybe_dead_p = rtx_equal_p (mem, has_hard_reg_initial_val (rmode, i)); |
| |
| if (GR_REGNO_P (i) || LOADABLE_CR_REGNO_P (i)) |
| F(maybe_dead_move (mem, gen_rtx_REG (rmode, i), maybe_dead_p)); |
| else if (rmode == DImode) |
| { |
| rtx insn; |
| int be = TARGET_BIG_ENDIAN ? 4 : 0; |
| |
| mem = gen_rtx_MEM (SImode, |
| plus_constant (Pmode, stack_pointer_rtx, |
| sp_offset - rss + be)); |
| |
| maybe_dead_move (gen_rtx_REG (SImode, REGSAVE_CONTROL_TEMP), |
| gen_rtx_REG (SImode, i), |
| maybe_dead_p); |
| maybe_dead_move (gen_rtx_REG (SImode, REGSAVE_CONTROL_TEMP+1), |
| gen_rtx_ZERO_EXTRACT (SImode, |
| gen_rtx_REG (DImode, i), |
| GEN_INT (32), |
| GEN_INT (32)), |
| maybe_dead_p); |
| insn = maybe_dead_move (mem, |
| gen_rtx_REG (SImode, REGSAVE_CONTROL_TEMP), |
| maybe_dead_p); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| |
| add_reg_note (insn, REG_FRAME_RELATED_EXPR, |
| gen_rtx_SET (VOIDmode, |
| copy_rtx (mem), |
| gen_rtx_REG (rmode, i))); |
| mem = gen_rtx_MEM (SImode, |
| plus_constant (Pmode, stack_pointer_rtx, |
| sp_offset - rss + (4-be))); |
| insn = maybe_dead_move (mem, |
| gen_rtx_REG (SImode, REGSAVE_CONTROL_TEMP+1), |
| maybe_dead_p); |
| } |
| else |
| { |
| rtx insn; |
| maybe_dead_move (gen_rtx_REG (rmode, REGSAVE_CONTROL_TEMP), |
| gen_rtx_REG (rmode, i), |
| maybe_dead_p); |
| insn = maybe_dead_move (mem, |
| gen_rtx_REG (rmode, REGSAVE_CONTROL_TEMP), |
| maybe_dead_p); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| |
| add_reg_note (insn, REG_FRAME_RELATED_EXPR, |
| gen_rtx_SET (VOIDmode, |
| copy_rtx (mem), |
| gen_rtx_REG (rmode, i))); |
| } |
| } |
| |
| if (frame_pointer_needed) |
| { |
| /* We've already adjusted down by sp_offset. Total $sp change |
| is reg_save_size + frame_size. We want a net change here of |
| just reg_save_size. */ |
| add_constant (FP_REGNO, SP_REGNO, sp_offset - reg_save_size, 1); |
| } |
| |
| add_constant (SP_REGNO, SP_REGNO, sp_offset-(reg_save_size+frame_size), 1); |
| |
| if (mep_interrupt_p ()) |
| { |
| mep_reload_pointer(GP_REGNO, "__sdabase"); |
| mep_reload_pointer(TP_REGNO, "__tpbase"); |
| } |
| } |
| |
| static void |
| mep_start_function (FILE *file, HOST_WIDE_INT hwi_local) |
| { |
| int local = hwi_local; |
| int frame_size = local + crtl->outgoing_args_size; |
| int reg_save_size; |
| int ffill; |
| int i, sp, skip; |
| int sp_offset; |
| int slot_map[FIRST_PSEUDO_REGISTER], si, sj; |
| |
| reg_save_size = mep_elimination_offset (ARG_POINTER_REGNUM, FRAME_POINTER_REGNUM); |
| frame_size = mep_elimination_offset (FRAME_POINTER_REGNUM, STACK_POINTER_REGNUM); |
| sp_offset = reg_save_size + frame_size; |
| |
| ffill = cfun->machine->frame_filler; |
| |
| if (cfun->machine->mep_frame_pointer_needed) |
| reg_names[FP_REGNO] = "$fp"; |
| else |
| reg_names[FP_REGNO] = "$8"; |
| |
| if (sp_offset == 0) |
| return; |
| |
| if (debug_info_level == DINFO_LEVEL_NONE) |
| { |
| fprintf (file, "\t# frame: %d", sp_offset); |
| if (reg_save_size) |
| fprintf (file, " %d regs", reg_save_size); |
| if (local) |
| fprintf (file, " %d locals", local); |
| if (crtl->outgoing_args_size) |
| fprintf (file, " %d args", crtl->outgoing_args_size); |
| fprintf (file, "\n"); |
| return; |
| } |
| |
| fprintf (file, "\t#\n"); |
| fprintf (file, "\t# Initial Frame Information:\n"); |
| if (sp_offset || !frame_pointer_needed) |
| fprintf (file, "\t# Entry ---------- 0\n"); |
| |
| /* Sort registers by save slots, so they're printed in the order |
| they appear in memory, not the order they're saved in. */ |
| for (si=0; si<FIRST_PSEUDO_REGISTER; si++) |
| slot_map[si] = si; |
| for (si=0; si<FIRST_PSEUDO_REGISTER-1; si++) |
| for (sj=si+1; sj<FIRST_PSEUDO_REGISTER; sj++) |
| if (cfun->machine->reg_save_slot[slot_map[si]] |
| > cfun->machine->reg_save_slot[slot_map[sj]]) |
| { |
| int t = slot_map[si]; |
| slot_map[si] = slot_map[sj]; |
| slot_map[sj] = t; |
| } |
| |
| sp = 0; |
| for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) |
| { |
| int rsize; |
| int r = slot_map[i]; |
| int rss = cfun->machine->reg_save_slot[r]; |
| |
| if (!mep_call_saves_register (r)) |
| continue; |
| |
| if ((r == TP_REGNO || r == GP_REGNO || r == LP_REGNO) |
| && (!mep_reg_set_in_function (r) |
| && !mep_interrupt_p ())) |
| continue; |
| |
| rsize = mep_reg_size(r); |
| skip = rss - (sp+rsize); |
| if (skip) |
| fprintf (file, "\t# %3d bytes for alignment\n", skip); |
| fprintf (file, "\t# %3d bytes for saved %-3s %3d($sp)\n", |
| rsize, reg_names[r], sp_offset - rss); |
| sp = rss; |
| } |
| |
| skip = reg_save_size - sp; |
| if (skip) |
| fprintf (file, "\t# %3d bytes for alignment\n", skip); |
| |
| if (frame_pointer_needed) |
| fprintf (file, "\t# FP ---> ---------- %d (sp-%d)\n", reg_save_size, sp_offset-reg_save_size); |
| if (local) |
| fprintf (file, "\t# %3d bytes for local vars\n", local); |
| if (ffill) |
| fprintf (file, "\t# %3d bytes for alignment\n", ffill); |
| if (crtl->outgoing_args_size) |
| fprintf (file, "\t# %3d bytes for outgoing args\n", |
| crtl->outgoing_args_size); |
| fprintf (file, "\t# SP ---> ---------- %d\n", sp_offset); |
| fprintf (file, "\t#\n"); |
| } |
| |
| |
| static int mep_prevent_lp_restore = 0; |
| static int mep_sibcall_epilogue = 0; |
| |
| void |
| mep_expand_epilogue (void) |
| { |
| int i, sp_offset = 0; |
| int reg_save_size = 0; |
| int frame_size; |
| int lp_temp = LP_REGNO, lp_slot = -1; |
| int really_need_stack_frame = get_frame_size() + crtl->outgoing_args_size; |
| int interrupt_handler = mep_interrupt_p (); |
| |
| if (profile_arc_flag == 2) |
| emit_insn (gen_mep_bb_trace_ret ()); |
| |
| reg_save_size = mep_elimination_offset (ARG_POINTER_REGNUM, FRAME_POINTER_REGNUM); |
| frame_size = mep_elimination_offset (FRAME_POINTER_REGNUM, STACK_POINTER_REGNUM); |
| |
| really_need_stack_frame |= mep_assign_save_slots (reg_save_size); |
| |
| if (frame_pointer_needed) |
| { |
| /* If we have a frame pointer, we won't have a reliable stack |
| pointer (alloca, you know), so rebase SP from FP */ |
| emit_move_insn (gen_rtx_REG (SImode, SP_REGNO), |
| gen_rtx_REG (SImode, FP_REGNO)); |
| sp_offset = reg_save_size; |
| } |
| else |
| { |
| /* SP is right under our local variable space. Adjust it if |
| needed. */ |
| sp_offset = reg_save_size + frame_size; |
| if (sp_offset >= 128) |
| { |
| add_constant (SP_REGNO, SP_REGNO, frame_size, 0); |
| sp_offset -= frame_size; |
| } |
| } |
| |
| /* This is backwards so that we restore the control and coprocessor |
| registers before the temporary registers we use to restore |
| them. */ |
| for (i=FIRST_PSEUDO_REGISTER-1; i>=1; i--) |
| if (mep_call_saves_register (i)) |
| { |
| enum machine_mode rmode; |
| int rss = cfun->machine->reg_save_slot[i]; |
| |
| if (mep_reg_size (i) == 8) |
| rmode = DImode; |
| else |
| rmode = SImode; |
| |
| if ((i == TP_REGNO || i == GP_REGNO || i == LP_REGNO) |
| && !(mep_reg_set_in_function (i) || interrupt_handler)) |
| continue; |
| if (mep_prevent_lp_restore && i == LP_REGNO) |
| continue; |
| if (!mep_prevent_lp_restore |
| && !interrupt_handler |
| && (i == 10 || i == 11)) |
| continue; |
| |
| if (GR_REGNO_P (i) || LOADABLE_CR_REGNO_P (i)) |
| emit_move_insn (gen_rtx_REG (rmode, i), |
| gen_rtx_MEM (rmode, |
| plus_constant (Pmode, stack_pointer_rtx, |
| sp_offset - rss))); |
| else |
| { |
| if (i == LP_REGNO && !mep_sibcall_epilogue && !interrupt_handler) |
| /* Defer this one so we can jump indirect rather than |
| copying the RA to $lp and "ret". EH epilogues |
| automatically skip this anyway. */ |
| lp_slot = sp_offset-rss; |
| else |
| { |
| emit_move_insn (gen_rtx_REG (rmode, REGSAVE_CONTROL_TEMP), |
| gen_rtx_MEM (rmode, |
| plus_constant (Pmode, |
| stack_pointer_rtx, |
| sp_offset-rss))); |
| emit_move_insn (gen_rtx_REG (rmode, i), |
| gen_rtx_REG (rmode, REGSAVE_CONTROL_TEMP)); |
| } |
| } |
| } |
| if (lp_slot != -1) |
| { |
| /* Restore this one last so we know it will be in the temp |
| register when we return by jumping indirectly via the temp. */ |
| emit_move_insn (gen_rtx_REG (SImode, REGSAVE_CONTROL_TEMP), |
| gen_rtx_MEM (SImode, |
| plus_constant (Pmode, stack_pointer_rtx, |
| lp_slot))); |
| lp_temp = REGSAVE_CONTROL_TEMP; |
| } |
| |
| |
| add_constant (SP_REGNO, SP_REGNO, sp_offset, 0); |
| |
| if (crtl->calls_eh_return && mep_prevent_lp_restore) |
| emit_insn (gen_addsi3 (gen_rtx_REG (SImode, SP_REGNO), |
| gen_rtx_REG (SImode, SP_REGNO), |
| cfun->machine->eh_stack_adjust)); |
| |
| if (mep_sibcall_epilogue) |
| return; |
| |
| if (mep_disinterrupt_p ()) |
| emit_insn (gen_mep_enable_int ()); |
| |
| if (mep_prevent_lp_restore) |
| { |
| emit_jump_insn (gen_eh_return_internal ()); |
| emit_barrier (); |
| } |
| else if (interrupt_handler) |
| emit_jump_insn (gen_mep_reti ()); |
| else |
| emit_jump_insn (gen_return_internal (gen_rtx_REG (SImode, lp_temp))); |
| } |
| |
| void |
| mep_expand_eh_return (rtx *operands) |
| { |
| if (GET_CODE (operands[0]) != REG || REGNO (operands[0]) != LP_REGNO) |
| { |
| rtx ra = gen_rtx_REG (Pmode, LP_REGNO); |
| emit_move_insn (ra, operands[0]); |
| operands[0] = ra; |
| } |
| |
| emit_insn (gen_eh_epilogue (operands[0])); |
| } |
| |
| void |
| mep_emit_eh_epilogue (rtx *operands ATTRIBUTE_UNUSED) |
| { |
| cfun->machine->eh_stack_adjust = gen_rtx_REG (Pmode, 0); |
| mep_prevent_lp_restore = 1; |
| mep_expand_epilogue (); |
| mep_prevent_lp_restore = 0; |
| } |
| |
| void |
| mep_expand_sibcall_epilogue (void) |
| { |
| mep_sibcall_epilogue = 1; |
| mep_expand_epilogue (); |
| mep_sibcall_epilogue = 0; |
| } |
| |
| static bool |
| mep_function_ok_for_sibcall (tree decl, tree exp ATTRIBUTE_UNUSED) |
| { |
| if (decl == NULL) |
| return false; |
| |
| if (mep_section_tag (DECL_RTL (decl)) == 'f') |
| return false; |
| |
| /* Can't call to a sibcall from an interrupt or disinterrupt function. */ |
| if (mep_interrupt_p () || mep_disinterrupt_p ()) |
| return false; |
| |
| return true; |
| } |
| |
| rtx |
| mep_return_stackadj_rtx (void) |
| { |
| return gen_rtx_REG (SImode, 10); |
| } |
| |
| rtx |
| mep_return_handler_rtx (void) |
| { |
| return gen_rtx_REG (SImode, LP_REGNO); |
| } |
| |
| void |
| mep_function_profiler (FILE *file) |
| { |
| /* Always right at the beginning of the function. */ |
| fprintf (file, "\t# mep function profiler\n"); |
| fprintf (file, "\tadd\t$sp, -8\n"); |
| fprintf (file, "\tsw\t$0, ($sp)\n"); |
| fprintf (file, "\tldc\t$0, $lp\n"); |
| fprintf (file, "\tsw\t$0, 4($sp)\n"); |
| fprintf (file, "\tbsr\t__mep_mcount\n"); |
| fprintf (file, "\tlw\t$0, 4($sp)\n"); |
| fprintf (file, "\tstc\t$0, $lp\n"); |
| fprintf (file, "\tlw\t$0, ($sp)\n"); |
| fprintf (file, "\tadd\t$sp, 8\n\n"); |
| } |
| |
| const char * |
| mep_emit_bb_trace_ret (void) |
| { |
| fprintf (asm_out_file, "\t# end of block profiling\n"); |
| fprintf (asm_out_file, "\tadd\t$sp, -8\n"); |
| fprintf (asm_out_file, "\tsw\t$0, ($sp)\n"); |
| fprintf (asm_out_file, "\tldc\t$0, $lp\n"); |
| fprintf (asm_out_file, "\tsw\t$0, 4($sp)\n"); |
| fprintf (asm_out_file, "\tbsr\t__bb_trace_ret\n"); |
| fprintf (asm_out_file, "\tlw\t$0, 4($sp)\n"); |
| fprintf (asm_out_file, "\tstc\t$0, $lp\n"); |
| fprintf (asm_out_file, "\tlw\t$0, ($sp)\n"); |
| fprintf (asm_out_file, "\tadd\t$sp, 8\n\n"); |
| return ""; |
| } |
| |
| #undef SAVE |
| #undef RESTORE |
| |
| /* Operand Printing. */ |
| |
| void |
| mep_print_operand_address (FILE *stream, rtx address) |
| { |
| if (GET_CODE (address) == MEM) |
| address = XEXP (address, 0); |
| else |
| /* cf: gcc.dg/asm-4.c. */ |
| gcc_assert (GET_CODE (address) == REG); |
| |
| mep_print_operand (stream, address, 0); |
| } |
| |
| static struct |
| { |
| char code; |
| const char *pattern; |
| const char *format; |
| } |
| const conversions[] = |
| { |
| { 0, "r", "0" }, |
| { 0, "m+ri", "3(2)" }, |
| { 0, "mr", "(1)" }, |
| { 0, "ms", "(1)" }, |
| { 0, "ml", "(1)" }, |
| { 0, "mLrs", "%lo(3)(2)" }, |
| { 0, "mLr+si", "%lo(4+5)(2)" }, |
| { 0, "m+ru2s", "%tpoff(5)(2)" }, |
| { 0, "m+ru3s", "%sdaoff(5)(2)" }, |
| { 0, "m+r+u2si", "%tpoff(6+7)(2)" }, |
| { 0, "m+ru2+si", "%tpoff(6+7)(2)" }, |
| { 0, "m+r+u3si", "%sdaoff(6+7)(2)" }, |
| { 0, "m+ru3+si", "%sdaoff(6+7)(2)" }, |
| { 0, "mi", "(1)" }, |
| { 0, "m+si", "(2+3)" }, |
| { 0, "m+li", "(2+3)" }, |
| { 0, "i", "0" }, |
| { 0, "s", "0" }, |
| { 0, "+si", "1+2" }, |
| { 0, "+u2si", "%tpoff(3+4)" }, |
| { 0, "+u3si", "%sdaoff(3+4)" }, |
| { 0, "l", "0" }, |
| { 'b', "i", "0" }, |
| { 'B', "i", "0" }, |
| { 'U', "i", "0" }, |
| { 'h', "i", "0" }, |
| { 'h', "Hs", "%hi(1)" }, |
| { 'I', "i", "0" }, |
| { 'I', "u2s", "%tpoff(2)" }, |
| { 'I', "u3s", "%sdaoff(2)" }, |
| { 'I', "+u2si", "%tpoff(3+4)" }, |
| { 'I', "+u3si", "%sdaoff(3+4)" }, |
| { 'J', "i", "0" }, |
| { 'P', "mr", "(1\\+),\\0" }, |
| { 'x', "i", "0" }, |
| { 0, 0, 0 } |
| }; |
| |
| static int |
| unique_bit_in (HOST_WIDE_INT i) |
| { |
| switch (i & 0xff) |
| { |
| case 0x01: case 0xfe: return 0; |
| case 0x02: case 0xfd: return 1; |
| case 0x04: case 0xfb: return 2; |
| case 0x08: case 0xf7: return 3; |
| case 0x10: case 0x7f: return 4; |
| case 0x20: case 0xbf: return 5; |
| case 0x40: case 0xdf: return 6; |
| case 0x80: case 0xef: return 7; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| static int |
| bit_size_for_clip (HOST_WIDE_INT i) |
| { |
| int rv; |
| |
| for (rv = 0; rv < 31; rv ++) |
| if (((HOST_WIDE_INT) 1 << rv) > i) |
| return rv + 1; |
| gcc_unreachable (); |
| } |
| |
| /* Print an operand to a assembler instruction. */ |
| |
| void |
| mep_print_operand (FILE *file, rtx x, int code) |
| { |
| int i, j; |
| const char *real_name; |
| |
| if (code == '<') |
| { |
| /* Print a mnemonic to do CR <- CR moves. Find out which intrinsic |
| we're using, then skip over the "mep_" part of its name. */ |
| const struct cgen_insn *insn; |
| |
| if (mep_get_move_insn (mep_cmov, &insn)) |
| fputs (cgen_intrinsics[insn->intrinsic] + 4, file); |
| else |
| mep_intrinsic_unavailable (mep_cmov); |
| return; |
| } |
| if (code == 'L') |
| { |
| switch (GET_CODE (x)) |
| { |
| case AND: |
| fputs ("clr", file); |
| return; |
| case IOR: |
| fputs ("set", file); |
| return; |
| case XOR: |
| fputs ("not", file); |
| return; |
| default: |
| output_operand_lossage ("invalid %%L code"); |
| } |
| } |
| if (code == 'M') |
| { |
| /* Print the second operand of a CR <- CR move. If we're using |
| a two-operand instruction (i.e., a real cmov), then just print |
| the operand normally. If we're using a "reg, reg, immediate" |
| instruction such as caddi3, print the operand followed by a |
| zero field. If we're using a three-register instruction, |
| print the operand twice. */ |
| const struct cgen_insn *insn; |
| |
| mep_print_operand (file, x, 0); |
| if (mep_get_move_insn (mep_cmov, &insn) |
| && insn_data[insn->icode].n_operands == 3) |
| { |
| fputs (", ", file); |
| if (insn_data[insn->icode].operand[2].predicate (x, VOIDmode)) |
| mep_print_operand (file, x, 0); |
| else |
| mep_print_operand (file, const0_rtx, 0); |
| } |
| return; |
| } |
| |
| encode_pattern (x); |
| for (i = 0; conversions[i].pattern; i++) |
| if (conversions[i].code == code |
| && strcmp(conversions[i].pattern, pattern) == 0) |
| { |
| for (j = 0; conversions[i].format[j]; j++) |
| if (conversions[i].format[j] == '\\') |
| { |
| fputc (conversions[i].format[j+1], file); |
| j++; |
| } |
| else if (ISDIGIT(conversions[i].format[j])) |
| { |
| rtx r = patternr[conversions[i].format[j] - '0']; |
| switch (GET_CODE (r)) |
| { |
| case REG: |
| fprintf (file, "%s", reg_names [REGNO (r)]); |
| break; |
| case CONST_INT: |
| switch (code) |
| { |
| case 'b': |
| fprintf (file, "%d", unique_bit_in (INTVAL (r))); |
| break; |
| case 'B': |
| fprintf (file, "%d", bit_size_for_clip (INTVAL (r))); |
| break; |
| case 'h': |
| fprintf (file, "0x%x", ((int) INTVAL (r) >> 16) & 0xffff); |
| break; |
| case 'U': |
| fprintf (file, "%d", bit_size_for_clip (INTVAL (r)) - 1); |
| break; |
| case 'J': |
| fprintf (file, "0x%x", (int) INTVAL (r) & 0xffff); |
| break; |
| case 'x': |
| if (INTVAL (r) & ~(HOST_WIDE_INT)0xff |
| && !(INTVAL (r) & 0xff)) |
| fprintf (file, HOST_WIDE_INT_PRINT_HEX, INTVAL(r)); |
| else |
| fprintf (file, HOST_WIDE_INT_PRINT_DEC, INTVAL(r)); |
| break; |
| case 'I': |
| if (INTVAL (r) & ~(HOST_WIDE_INT)0xff |
| && conversions[i].format[j+1] == 0) |
| { |
| fprintf (file, HOST_WIDE_INT_PRINT_DEC, INTVAL (r)); |
| fprintf (file, " # 0x%x", (int) INTVAL(r) & 0xffff); |
| } |
| else |
| fprintf (file, HOST_WIDE_INT_PRINT_DEC, INTVAL(r)); |
| break; |
| default: |
| fprintf (file, HOST_WIDE_INT_PRINT_DEC, INTVAL(r)); |
| break; |
| } |
| break; |
| case CONST_DOUBLE: |
| fprintf(file, "[const_double 0x%lx]", |
| (unsigned long) CONST_DOUBLE_HIGH(r)); |
| break; |
| case SYMBOL_REF: |
| real_name = targetm.strip_name_encoding (XSTR (r, 0)); |
| assemble_name (file, real_name); |
| break; |
| case LABEL_REF: |
| output_asm_label (r); |
| break; |
| default: |
| fprintf (stderr, "don't know how to print this operand:"); |
| debug_rtx (r); |
| gcc_unreachable (); |
| } |
| } |
| else |
| { |
| if (conversions[i].format[j] == '+' |
| && (!code || code == 'I') |
| && ISDIGIT (conversions[i].format[j+1]) |
| && GET_CODE (patternr[conversions[i].format[j+1] - '0']) == CONST_INT |
| && INTVAL (patternr[conversions[i].format[j+1] - '0']) < 0) |
| continue; |
| fputc(conversions[i].format[j], file); |
| } |
| break; |
| } |
| if (!conversions[i].pattern) |
| { |
| error ("unconvertible operand %c %qs", code?code:'-', pattern); |
| debug_rtx(x); |
| } |
| |
| return; |
| } |
| |
| void |
| mep_final_prescan_insn (rtx insn, rtx *operands ATTRIBUTE_UNUSED, |
| int noperands ATTRIBUTE_UNUSED) |
| { |
| /* Despite the fact that MeP is perfectly capable of branching and |
| doing something else in the same bundle, gcc does jump |
| optimization *after* scheduling, so we cannot trust the bundling |
| flags on jump instructions. */ |
| if (GET_MODE (insn) == BImode |
| && get_attr_slots (insn) != SLOTS_CORE) |
| fputc ('+', asm_out_file); |
| } |
| |
| /* Function args in registers. */ |
| |
| static void |
| mep_setup_incoming_varargs (cumulative_args_t cum, |
| enum machine_mode mode ATTRIBUTE_UNUSED, |
| tree type ATTRIBUTE_UNUSED, int *pretend_size, |
| int second_time ATTRIBUTE_UNUSED) |
| { |
| int nsave = 4 - (get_cumulative_args (cum)->nregs + 1); |
| |
| if (nsave > 0) |
| cfun->machine->arg_regs_to_save = nsave; |
| *pretend_size = nsave * 4; |
| } |
| |
| static int |
| bytesize (const_tree type, enum machine_mode mode) |
| { |
| if (mode == BLKmode) |
| return int_size_in_bytes (type); |
| return GET_MODE_SIZE (mode); |
| } |
| |
| static rtx |
| mep_expand_builtin_saveregs (void) |
| { |
| int bufsize, i, ns; |
| rtx regbuf; |
| |
| ns = cfun->machine->arg_regs_to_save; |
| if (TARGET_IVC2) |
| { |
| bufsize = 8 * ((ns + 1) / 2) + 8 * ns; |
| regbuf = assign_stack_local (SImode, bufsize, 64); |
| } |
| else |
| { |
| bufsize = ns * 4; |
| regbuf = assign_stack_local (SImode, bufsize, 32); |
| } |
| |
| move_block_from_reg (5-ns, regbuf, ns); |
| |
| if (TARGET_IVC2) |
| { |
| rtx tmp = gen_rtx_MEM (DImode, XEXP (regbuf, 0)); |
| int ofs = 8 * ((ns+1)/2); |
| |
| for (i=0; i<ns; i++) |
| { |
| int rn = (4-ns) + i + 49; |
| rtx ptr; |
| |
| ptr = offset_address (tmp, GEN_INT (ofs), 2); |
| emit_move_insn (ptr, gen_rtx_REG (DImode, rn)); |
| ofs += 8; |
| } |
| } |
| return XEXP (regbuf, 0); |
| } |
| |
| #define VECTOR_TYPE_P(t) (TREE_CODE(t) == VECTOR_TYPE) |
| |
| static tree |
| mep_build_builtin_va_list (void) |
| { |
| tree f_next_gp, f_next_gp_limit, f_next_cop, f_next_stack; |
| tree record; |
| |
| |
| record = (*lang_hooks.types.make_type) (RECORD_TYPE); |
| |
| f_next_gp = build_decl (BUILTINS_LOCATION, FIELD_DECL, |
| get_identifier ("__va_next_gp"), ptr_type_node); |
| f_next_gp_limit = build_decl (BUILTINS_LOCATION, FIELD_DECL, |
| get_identifier ("__va_next_gp_limit"), |
| ptr_type_node); |
| f_next_cop = build_decl (BUILTINS_LOCATION, FIELD_DECL, get_identifier ("__va_next_cop"), |
| ptr_type_node); |
| f_next_stack = build_decl (BUILTINS_LOCATION, FIELD_DECL, get_identifier ("__va_next_stack"), |
| ptr_type_node); |
| |
| DECL_FIELD_CONTEXT (f_next_gp) = record; |
| DECL_FIELD_CONTEXT (f_next_gp_limit) = record; |
| DECL_FIELD_CONTEXT (f_next_cop) = record; |
| DECL_FIELD_CONTEXT (f_next_stack) = record; |
| |
| TYPE_FIELDS (record) = f_next_gp; |
| DECL_CHAIN (f_next_gp) = f_next_gp_limit; |
| DECL_CHAIN (f_next_gp_limit) = f_next_cop; |
| DECL_CHAIN (f_next_cop) = f_next_stack; |
| |
| layout_type (record); |
| |
| return record; |
| } |
| |
| static void |
| mep_expand_va_start (tree valist, rtx nextarg) |
| { |
| tree f_next_gp, f_next_gp_limit, f_next_cop, f_next_stack; |
| tree next_gp, next_gp_limit, next_cop, next_stack; |
| tree t, u; |
| int ns; |
| |
| ns = cfun->machine->arg_regs_to_save; |
| |
| f_next_gp = TYPE_FIELDS (va_list_type_node); |
| f_next_gp_limit = DECL_CHAIN (f_next_gp); |
| f_next_cop = DECL_CHAIN (f_next_gp_limit); |
| f_next_stack = DECL_CHAIN (f_next_cop); |
| |
| next_gp = build3 (COMPONENT_REF, TREE_TYPE (f_next_gp), valist, f_next_gp, |
| NULL_TREE); |
| next_gp_limit = build3 (COMPONENT_REF, TREE_TYPE (f_next_gp_limit), |
| valist, f_next_gp_limit, NULL_TREE); |
| next_cop = build3 (COMPONENT_REF, TREE_TYPE (f_next_cop), valist, f_next_cop, |
| NULL_TREE); |
| next_stack = build3 (COMPONENT_REF, TREE_TYPE (f_next_stack), |
| valist, f_next_stack, NULL_TREE); |
| |
| /* va_list.next_gp = expand_builtin_saveregs (); */ |
| u = make_tree (sizetype, expand_builtin_saveregs ()); |
| u = fold_convert (ptr_type_node, u); |
| t = build2 (MODIFY_EXPR, ptr_type_node, next_gp, u); |
| TREE_SIDE_EFFECTS (t) = 1; |
| expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL); |
| |
| /* va_list.next_gp_limit = va_list.next_gp + 4 * ns; */ |
| u = fold_build_pointer_plus_hwi (u, 4 * ns); |
| t = build2 (MODIFY_EXPR, ptr_type_node, next_gp_limit, u); |
| TREE_SIDE_EFFECTS (t) = 1; |
| expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL); |
| |
| u = fold_build_pointer_plus_hwi (u, 8 * ((ns+1)/2)); |
| /* va_list.next_cop = ROUND_UP(va_list.next_gp_limit,8); */ |
| t = build2 (MODIFY_EXPR, ptr_type_node, next_cop, u); |
| TREE_SIDE_EFFECTS (t) = 1; |
| expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL); |
| |
| /* va_list.next_stack = nextarg; */ |
| u = make_tree (ptr_type_node, nextarg); |
| t = build2 (MODIFY_EXPR, ptr_type_node, next_stack, u); |
| TREE_SIDE_EFFECTS (t) = 1; |
| expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL); |
| } |
| |
| static tree |
| mep_gimplify_va_arg_expr (tree valist, tree type, |
| gimple_seq *pre_p, |
| gimple_seq *post_p ATTRIBUTE_UNUSED) |
| { |
| HOST_WIDE_INT size, rsize; |
| bool by_reference, ivc2_vec; |
| tree f_next_gp, f_next_gp_limit, f_next_cop, f_next_stack; |
| tree next_gp, next_gp_limit, next_cop, next_stack; |
| tree label_sover, label_selse; |
| tree tmp, res_addr; |
| |
| ivc2_vec = TARGET_IVC2 && VECTOR_TYPE_P (type); |
| |
| size = int_size_in_bytes (type); |
| by_reference = (size > (ivc2_vec ? 8 : 4)) || (size <= 0); |
| |
| if (by_reference) |
| { |
| type = build_pointer_type (type); |
| size = 4; |
| } |
| rsize = (size + UNITS_PER_WORD - 1) & -UNITS_PER_WORD; |
| |
| f_next_gp = TYPE_FIELDS (va_list_type_node); |
| f_next_gp_limit = DECL_CHAIN (f_next_gp); |
| f_next_cop = DECL_CHAIN (f_next_gp_limit); |
| f_next_stack = DECL_CHAIN (f_next_cop); |
| |
| next_gp = build3 (COMPONENT_REF, TREE_TYPE (f_next_gp), valist, f_next_gp, |
| NULL_TREE); |
| next_gp_limit = build3 (COMPONENT_REF, TREE_TYPE (f_next_gp_limit), |
| valist, f_next_gp_limit, NULL_TREE); |
| next_cop = build3 (COMPONENT_REF, TREE_TYPE (f_next_cop), valist, f_next_cop, |
| NULL_TREE); |
| next_stack = build3 (COMPONENT_REF, TREE_TYPE (f_next_stack), |
| valist, f_next_stack, NULL_TREE); |
| |
| /* if f_next_gp < f_next_gp_limit |
| IF (VECTOR_P && IVC2) |
| val = *f_next_cop; |
| ELSE |
| val = *f_next_gp; |
| f_next_gp += 4; |
| f_next_cop += 8; |
| else |
| label_selse: |
| val = *f_next_stack; |
| f_next_stack += rsize; |
| label_sover: |
| */ |
| |
| label_sover = create_artificial_label (UNKNOWN_LOCATION); |
| label_selse = create_artificial_label (UNKNOWN_LOCATION); |
| res_addr = create_tmp_var (ptr_type_node, NULL); |
| |
| tmp = build2 (GE_EXPR, boolean_type_node, next_gp, |
| unshare_expr (next_gp_limit)); |
| tmp = build3 (COND_EXPR, void_type_node, tmp, |
| build1 (GOTO_EXPR, void_type_node, |
| unshare_expr (label_selse)), |
| NULL_TREE); |
| gimplify_and_add (tmp, pre_p); |
| |
| if (ivc2_vec) |
| { |
| tmp = build2 (MODIFY_EXPR, void_type_node, res_addr, next_cop); |
| gimplify_and_add (tmp, pre_p); |
| } |
| else |
| { |
| tmp = build2 (MODIFY_EXPR, void_type_node, res_addr, next_gp); |
| gimplify_and_add (tmp, pre_p); |
| } |
| |
| tmp = fold_build_pointer_plus_hwi (unshare_expr (next_gp), 4); |
| gimplify_assign (unshare_expr (next_gp), tmp, pre_p); |
| |
| tmp = fold_build_pointer_plus_hwi (unshare_expr (next_cop), 8); |
| gimplify_assign (unshare_expr (next_cop), tmp, pre_p); |
| |
| tmp = build1 (GOTO_EXPR, void_type_node, unshare_expr (label_sover)); |
| gimplify_and_add (tmp, pre_p); |
| |
| /* - - */ |
| |
| tmp = build1 (LABEL_EXPR, void_type_node, unshare_expr (label_selse)); |
| gimplify_and_add (tmp, pre_p); |
| |
| tmp = build2 (MODIFY_EXPR, void_type_node, res_addr, unshare_expr (next_stack)); |
| gimplify_and_add (tmp, pre_p); |
| |
| tmp = fold_build_pointer_plus_hwi (unshare_expr (next_stack), rsize); |
| gimplify_assign (unshare_expr (next_stack), tmp, pre_p); |
| |
| /* - - */ |
| |
| tmp = build1 (LABEL_EXPR, void_type_node, unshare_expr (label_sover)); |
| gimplify_and_add (tmp, pre_p); |
| |
| res_addr = fold_convert (build_pointer_type (type), res_addr); |
| |
| if (by_reference) |
| res_addr = build_va_arg_indirect_ref (res_addr); |
| |
| return build_va_arg_indirect_ref (res_addr); |
| } |
| |
| void |
| mep_init_cumulative_args (CUMULATIVE_ARGS *pcum, tree fntype, |
| rtx libname ATTRIBUTE_UNUSED, |
| tree fndecl ATTRIBUTE_UNUSED) |
| { |
| pcum->nregs = 0; |
| |
| if (fntype && lookup_attribute ("vliw", TYPE_ATTRIBUTES (fntype))) |
| pcum->vliw = 1; |
| else |
| pcum->vliw = 0; |
| } |
| |
| /* The ABI is thus: Arguments are in $1, $2, $3, $4, stack. Arguments |
| larger than 4 bytes are passed indirectly. Return value in 0, |
| unless bigger than 4 bytes, then the caller passes a pointer as the |
| first arg. For varargs, we copy $1..$4 to the stack. */ |
| |
| static rtx |
| mep_function_arg (cumulative_args_t cum_v, enum machine_mode mode, |
| const_tree type ATTRIBUTE_UNUSED, |
| bool named ATTRIBUTE_UNUSED) |
| { |
| CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v); |
| |
| /* VOIDmode is a signal for the backend to pass data to the call |
| expander via the second operand to the call pattern. We use |
| this to determine whether to use "jsr" or "jsrv". */ |
| if (mode == VOIDmode) |
| return GEN_INT (cum->vliw); |
| |
| /* If we havn't run out of argument registers, return the next. */ |
| if (cum->nregs < 4) |
| { |
| if (type && TARGET_IVC2 && VECTOR_TYPE_P (type)) |
| return gen_rtx_REG (mode, cum->nregs + 49); |
| else |
| return gen_rtx_REG (mode, cum->nregs + 1); |
| } |
| |
| /* Otherwise the argument goes on the stack. */ |
| return NULL_RTX; |
| } |
| |
| static bool |
| mep_pass_by_reference (cumulative_args_t cum ATTRIBUTE_UNUSED, |
| enum machine_mode mode, |
| const_tree type, |
| bool named ATTRIBUTE_UNUSED) |
| { |
| int size = bytesize (type, mode); |
| |
| /* This is non-obvious, but yes, large values passed after we've run |
| out of registers are *still* passed by reference - we put the |
| address of the parameter on the stack, as well as putting the |
| parameter itself elsewhere on the stack. */ |
| |
| if (size <= 0 || size > 8) |
| return true; |
| if (size <= 4) |
| return false; |
| if (TARGET_IVC2 && get_cumulative_args (cum)->nregs < 4 |
| && type != NULL_TREE && VECTOR_TYPE_P (type)) |
| return false; |
| return true; |
| } |
| |
| static void |
| mep_function_arg_advance (cumulative_args_t pcum, |
| enum machine_mode mode ATTRIBUTE_UNUSED, |
| const_tree type ATTRIBUTE_UNUSED, |
| bool named ATTRIBUTE_UNUSED) |
| { |
| get_cumulative_args (pcum)->nregs += 1; |
| } |
| |
| bool |
| mep_return_in_memory (const_tree type, const_tree decl ATTRIBUTE_UNUSED) |
| { |
| int size = bytesize (type, BLKmode); |
| if (TARGET_IVC2 && VECTOR_TYPE_P (type)) |
| return size > 0 && size <= 8 ? 0 : 1; |
| return size > 0 && size <= 4 ? 0 : 1; |
| } |
| |
| static bool |
| mep_narrow_volatile_bitfield (void) |
| { |
| return true; |
| return false; |
| } |
| |
| /* Implement FUNCTION_VALUE. All values are returned in $0. */ |
| |
| rtx |
| mep_function_value (const_tree type, const_tree func ATTRIBUTE_UNUSED) |
| { |
| if (TARGET_IVC2 && VECTOR_TYPE_P (type)) |
| return gen_rtx_REG (TYPE_MODE (type), 48); |
| return gen_rtx_REG (TYPE_MODE (type), RETURN_VALUE_REGNUM); |
| } |
| |
| /* Implement LIBCALL_VALUE, using the same rules as mep_function_value. */ |
| |
| rtx |
| mep_libcall_value (enum machine_mode mode) |
| { |
| return gen_rtx_REG (mode, RETURN_VALUE_REGNUM); |
| } |
| |
| /* Handle pipeline hazards. */ |
| |
| typedef enum { op_none, op_stc, op_fsft, op_ret } op_num; |
| static const char *opnames[] = { "", "stc", "fsft", "ret" }; |
| |
| static int prev_opcode = 0; |
| |
| /* This isn't as optimal as it could be, because we don't know what |
| control register the STC opcode is storing in. We only need to add |
| the nop if it's the relevant register, but we add it for irrelevant |
| registers also. */ |
| |
| void |
| mep_asm_output_opcode (FILE *file, const char *ptr) |
| { |
| int this_opcode = op_none; |
| const char *hazard = 0; |
| |
| switch (*ptr) |
| { |
| case 'f': |
| if (strncmp (ptr, "fsft", 4) == 0 && !ISGRAPH (ptr[4])) |
| this_opcode = op_fsft; |
| break; |
| case 'r': |
| if (strncmp (ptr, "ret", 3) == 0 && !ISGRAPH (ptr[3])) |
| this_opcode = op_ret; |
| break; |
| case 's': |
| if (strncmp (ptr, "stc", 3) == 0 && !ISGRAPH (ptr[3])) |
| this_opcode = op_stc; |
| break; |
| } |
| |
| if (prev_opcode == op_stc && this_opcode == op_fsft) |
| hazard = "nop"; |
| if (prev_opcode == op_stc && this_opcode == op_ret) |
| hazard = "nop"; |
| |
| if (hazard) |
| fprintf(file, "%s\t# %s-%s hazard\n\t", |
| hazard, opnames[prev_opcode], opnames[this_opcode]); |
| |
| prev_opcode = this_opcode; |
| } |
| |
| /* Handle attributes. */ |
| |
| static tree |
| mep_validate_based_tiny (tree *node, tree name, tree args, |
| int flags ATTRIBUTE_UNUSED, bool *no_add) |
| { |
| if (TREE_CODE (*node) != VAR_DECL |
| && TREE_CODE (*node) != POINTER_TYPE |
| && TREE_CODE (*node) != TYPE_DECL) |
| { |
| warning (0, "%qE attribute only applies to variables", name); |
| *no_add = true; |
| } |
| else if (args == NULL_TREE && TREE_CODE (*node) == VAR_DECL) |
| { |
| if (! (TREE_PUBLIC (*node) || TREE_STATIC (*node))) |
| { |
| warning (0, "address region attributes not allowed with auto storage class"); |
| *no_add = true; |
| } |
| /* Ignore storage attribute of pointed to variable: char __far * x; */ |
| if (TREE_TYPE (*node) && TREE_CODE (TREE_TYPE (*node)) == POINTER_TYPE) |
| { |
| warning (0, "address region attributes on pointed-to types ignored"); |
| *no_add = true; |
| } |
| } |
| |
| return NULL_TREE; |
| } |
| |
| static int |
| mep_multiple_address_regions (tree list, bool check_section_attr) |
| { |
| tree a; |
| int count_sections = 0; |
| int section_attr_count = 0; |
| |
| for (a = list; a; a = TREE_CHAIN (a)) |
| { |
| if (is_attribute_p ("based", TREE_PURPOSE (a)) |
| || is_attribute_p ("tiny", TREE_PURPOSE (a)) |
| || is_attribute_p ("near", TREE_PURPOSE (a)) |
| || is_attribute_p ("far", TREE_PURPOSE (a)) |
| || is_attribute_p ("io", TREE_PURPOSE (a))) |
| count_sections ++; |
| if (check_section_attr) |
| section_attr_count += is_attribute_p ("section", TREE_PURPOSE (a)); |
| } |
| |
| if (check_section_attr) |
| return section_attr_count; |
| else |
| return count_sections; |
| } |
| |
| #define MEP_ATTRIBUTES(decl) \ |
| (TYPE_P (decl)) ? TYPE_ATTRIBUTES (decl) \ |
| : DECL_ATTRIBUTES (decl) \ |
| ? (DECL_ATTRIBUTES (decl)) \ |
| : TYPE_ATTRIBUTES (TREE_TYPE (decl)) |
| |
| static tree |
| mep_validate_near_far (tree *node, tree name, tree args, |
| int flags ATTRIBUTE_UNUSED, bool *no_add) |
| { |
| if (TREE_CODE (*node) != VAR_DECL |
| && TREE_CODE (*node) != FUNCTION_DECL |
| && TREE_CODE (*node) != METHOD_TYPE |
| && TREE_CODE (*node) != POINTER_TYPE |
| && TREE_CODE (*node) != TYPE_DECL) |
| { |
| warning (0, "%qE attribute only applies to variables and functions", |
| name); |
| *no_add = true; |
| } |
| else if (args == NULL_TREE && TREE_CODE (*node) == VAR_DECL) |
| { |
| if (! (TREE_PUBLIC (*node) || TREE_STATIC (*node))) |
| { |
| warning (0, "address region attributes not allowed with auto storage class"); |
| *no_add = true; |
| } |
| /* Ignore storage attribute of pointed to variable: char __far * x; */ |
| if (TREE_TYPE (*node) && TREE_CODE (TREE_TYPE (*node)) == POINTER_TYPE) |
| { |
| warning (0, "address region attributes on pointed-to types ignored"); |
| *no_add = true; |
| } |
| } |
| else if (mep_multiple_address_regions (MEP_ATTRIBUTES (*node), false) > 0) |
| { |
| warning (0, "duplicate address region attribute %qE in declaration of %qE on line %d", |
| name, DECL_NAME (*node), DECL_SOURCE_LINE (*node)); |
| DECL_ATTRIBUTES (*node) = NULL_TREE; |
| } |
| return NULL_TREE; |
| } |
| |
| static tree |
| mep_validate_disinterrupt (tree *node, tree name, tree args ATTRIBUTE_UNUSED, |
| int flags ATTRIBUTE_UNUSED, bool *no_add) |
| { |
| if (TREE_CODE (*node) != FUNCTION_DECL |
| && TREE_CODE (*node) != METHOD_TYPE) |
| { |
| warning (0, "%qE attribute only applies to functions", name); |
| *no_add = true; |
| } |
| return NULL_TREE; |
| } |
| |
| static tree |
| mep_validate_interrupt (tree *node, tree name, tree args ATTRIBUTE_UNUSED, |
| int flags ATTRIBUTE_UNUSED, bool *no_add) |
| { |
| tree function_type; |
| |
| if (TREE_CODE (*node) != FUNCTION_DECL) |
| { |
| warning (0, "%qE attribute only applies to functions", name); |
| *no_add = true; |
| return NULL_TREE; |
| } |
| |
| if (DECL_DECLARED_INLINE_P (*node)) |
| error ("cannot inline interrupt function %qE", DECL_NAME (*node)); |
| DECL_UNINLINABLE (*node) = 1; |
| |
| function_type = TREE_TYPE (*node); |
| |
| if (TREE_TYPE (function_type) != void_type_node) |
| error ("interrupt function must have return type of void"); |
| |
| if (prototype_p (function_type) |
| && (TREE_VALUE (TYPE_ARG_TYPES (function_type)) != void_type_node |
| || TREE_CHAIN (TYPE_ARG_TYPES (function_type)) != NULL_TREE)) |
| error ("interrupt function must have no arguments"); |
| |
| return NULL_TREE; |
| } |
| |
| static tree |
| mep_validate_io_cb (tree *node, tree name, tree args, |
| int flags ATTRIBUTE_UNUSED, bool *no_add) |
| { |
| if (TREE_CODE (*node) != VAR_DECL) |
| { |
| warning (0, "%qE attribute only applies to variables", name); |
| *no_add = true; |
| } |
| |
| if (args != NULL_TREE) |
| { |
| if (TREE_CODE (TREE_VALUE (args)) == NON_LVALUE_EXPR) |
| TREE_VALUE (args) = TREE_OPERAND (TREE_VALUE (args), 0); |
| if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST) |
| { |
| warning (0, "%qE attribute allows only an integer constant argument", |
| name); |
| *no_add = true; |
| } |
| } |
| |
| if (*no_add == false && !TARGET_IO_NO_VOLATILE) |
| TREE_THIS_VOLATILE (*node) = 1; |
| |
| return NULL_TREE; |
| } |
| |
| static tree |
| mep_validate_vliw (tree *node, tree name, tree args ATTRIBUTE_UNUSED, |
| int flags ATTRIBUTE_UNUSED, bool *no_add) |
| { |
| if (TREE_CODE (*node) != FUNCTION_TYPE |
| && TREE_CODE (*node) != FUNCTION_DECL |
| && TREE_CODE (*node) != METHOD_TYPE |
| && TREE_CODE (*node) != FIELD_DECL |
| && TREE_CODE (*node) != TYPE_DECL) |
| { |
| static int gave_pointer_note = 0; |
| static int gave_array_note = 0; |
| static const char * given_type = NULL; |
| |
| given_type = tree_code_name[TREE_CODE (*node)]; |
| if (TREE_CODE (*node) == POINTER_TYPE) |
| given_type = "pointers"; |
| if (TREE_CODE (*node) == ARRAY_TYPE) |
| given_type = "arrays"; |
| |
| if (given_type) |
| warning (0, "%qE attribute only applies to functions, not %s", |
| name, given_type); |
| else |
| warning (0, "%qE attribute only applies to functions", |
| name); |
| *no_add = true; |
| |
| if (TREE_CODE (*node) == POINTER_TYPE |
| && !gave_pointer_note) |
| { |
| inform (input_location, |
| "to describe a pointer to a VLIW function, use syntax like this:\n%s", |
| " typedef int (__vliw *vfuncptr) ();"); |
| gave_pointer_note = 1; |
| } |
| |
| if (TREE_CODE (*node) == ARRAY_TYPE |
| && !gave_array_note) |
| { |
| inform (input_location, |
| "to describe an array of VLIW function pointers, use syntax like this:\n%s", |
| " typedef int (__vliw *vfuncptr[]) ();"); |
| gave_array_note = 1; |
| } |
| } |
| if (!TARGET_VLIW) |
| error ("VLIW functions are not allowed without a VLIW configuration"); |
| return NULL_TREE; |
| } |
| |
| static const struct attribute_spec mep_attribute_table[11] = |
| { |
| /* name min max decl type func handler |
| affects_type_identity */ |
| { "based", 0, 0, false, false, false, mep_validate_based_tiny, false }, |
| { "tiny", 0, 0, false, false, false, mep_validate_based_tiny, false }, |
| { "near", 0, 0, false, false, false, mep_validate_near_far, false }, |
| { "far", 0, 0, false, false, false, mep_validate_near_far, false }, |
| { "disinterrupt", 0, 0, false, false, false, mep_validate_disinterrupt, |
| false }, |
| { "interrupt", 0, 0, false, false, false, mep_validate_interrupt, false }, |
| { "io", 0, 1, false, false, false, mep_validate_io_cb, false }, |
| { "cb", 0, 1, false, false, false, mep_validate_io_cb, false }, |
| { "vliw", 0, 0, false, true, false, mep_validate_vliw, false }, |
| { NULL, 0, 0, false, false, false, NULL, false } |
| }; |
| |
| static bool |
| mep_function_attribute_inlinable_p (const_tree callee) |
| { |
| tree attrs = TYPE_ATTRIBUTES (TREE_TYPE (callee)); |
| if (!attrs) attrs = DECL_ATTRIBUTES (callee); |
| return (lookup_attribute ("disinterrupt", attrs) == 0 |
| && lookup_attribute ("interrupt", attrs) == 0); |
| } |
| |
| static bool |
| mep_can_inline_p (tree caller, tree callee) |
| { |
| if (TREE_CODE (callee) == ADDR_EXPR) |
| callee = TREE_OPERAND (callee, 0); |
| |
| if (!mep_vliw_function_p (caller) |
| && mep_vliw_function_p (callee)) |
| { |
| return false; |
| } |
| return true; |
| } |
| |
| #define FUNC_CALL 1 |
| #define FUNC_DISINTERRUPT 2 |
| |
| |
| struct GTY(()) pragma_entry { |
| int used; |
| int flag; |
| const char *funcname; |
| }; |
| typedef struct pragma_entry pragma_entry; |
| |
| /* Hash table of farcall-tagged sections. */ |
| static GTY((param_is (pragma_entry))) htab_t pragma_htab; |
| |
| static int |
| pragma_entry_eq (const void *p1, const void *p2) |
| { |
| const pragma_entry *old = (const pragma_entry *) p1; |
| const char *new_name = (const char *) p2; |
| |
| return strcmp (old->funcname, new_name) == 0; |
| } |
| |
| static hashval_t |
| pragma_entry_hash (const void *p) |
| { |
| const pragma_entry *old = (const pragma_entry *) p; |
| return htab_hash_string (old->funcname); |
| } |
| |
| static void |
| mep_note_pragma_flag (const char *funcname, int flag) |
| { |
| pragma_entry **slot; |
| |
| if (!pragma_htab) |
| pragma_htab = htab_create_ggc (31, pragma_entry_hash, |
| pragma_entry_eq, NULL); |
| |
| slot = (pragma_entry **) |
| htab_find_slot_with_hash (pragma_htab, funcname, |
| htab_hash_string (funcname), INSERT); |
| |
| if (!*slot) |
| { |
| *slot = ggc_alloc_pragma_entry (); |
| (*slot)->flag = 0; |
| (*slot)->used = 0; |
| (*slot)->funcname = ggc_strdup (funcname); |
| } |
| (*slot)->flag |= flag; |
| } |
| |
| static bool |
| mep_lookup_pragma_flag (const char *funcname, int flag) |
| { |
| pragma_entry **slot; |
| |
| if (!pragma_htab) |
| return false; |
| |
| if (funcname[0] == '@' && funcname[2] == '.') |
| funcname += 3; |
| |
| slot = (pragma_entry **) |
| htab_find_slot_with_hash (pragma_htab, funcname, |
| htab_hash_string (funcname), NO_INSERT); |
| if (slot && *slot && ((*slot)->flag & flag)) |
| { |
| (*slot)->used |= flag; |
| return true; |
| } |
| return false; |
| } |
| |
| bool |
| mep_lookup_pragma_call (const char *funcname) |
| { |
| return mep_lookup_pragma_flag (funcname, FUNC_CALL); |
| } |
| |
| void |
| mep_note_pragma_call (const char *funcname) |
| { |
| mep_note_pragma_flag (funcname, FUNC_CALL); |
| } |
| |
| bool |
| mep_lookup_pragma_disinterrupt (const char *funcname) |
| { |
| return mep_lookup_pragma_flag (funcname, FUNC_DISINTERRUPT); |
| } |
| |
| void |
| mep_note_pragma_disinterrupt (const char *funcname) |
| { |
| mep_note_pragma_flag (funcname, FUNC_DISINTERRUPT); |
| } |
| |
| static int |
| note_unused_pragma_disinterrupt (void **slot, void *data ATTRIBUTE_UNUSED) |
| { |
| const pragma_entry *d = (const pragma_entry *)(*slot); |
| |
| if ((d->flag & FUNC_DISINTERRUPT) |
| && !(d->used & FUNC_DISINTERRUPT)) |
| warning (0, "\"#pragma disinterrupt %s\" not used", d->funcname); |
| return 1; |
| } |
| |
| void |
| mep_file_cleanups (void) |
| { |
| if (pragma_htab) |
| htab_traverse (pragma_htab, note_unused_pragma_disinterrupt, NULL); |
| } |
| |
| /* These three functions provide a bridge between the pramgas that |
| affect register classes, and the functions that maintain them. We |
| can't call those functions directly as pragma handling is part of |
| the front end and doesn't have direct access to them. */ |
| |
| void |
| mep_save_register_info (void) |
| { |
| save_register_info (); |
| } |
| |
| void |
| mep_reinit_regs (void) |
| { |
| reinit_regs (); |
| } |
| |
| void |
| mep_init_regs (void) |
| { |
| init_regs (); |
| } |
| |
| |
| |
| static int |
| mep_attrlist_to_encoding (tree list, tree decl) |
| { |
| if (mep_multiple_address_regions (list, false) > 1) |
| { |
| warning (0, "duplicate address region attribute %qE in declaration of %qE on line %d", |
| TREE_PURPOSE (TREE_CHAIN (list)), |
| DECL_NAME (decl), |
| DECL_SOURCE_LINE (decl)); |
| TREE_CHAIN (list) = NULL_TREE; |
| } |
| |
| while (list) |
| { |
| if (is_attribute_p ("based", TREE_PURPOSE (list))) |
| return 'b'; |
| if (is_attribute_p ("tiny", TREE_PURPOSE (list))) |
| return 't'; |
| if (is_attribute_p ("near", TREE_PURPOSE (list))) |
| return 'n'; |
| if (is_attribute_p ("far", TREE_PURPOSE (list))) |
| return 'f'; |
| if (is_attribute_p ("io", TREE_PURPOSE (list))) |
| { |
| if (TREE_VALUE (list) |
| && TREE_VALUE (TREE_VALUE (list)) |
| && TREE_CODE (TREE_VALUE (TREE_VALUE (list))) == INTEGER_CST) |
| { |
| int location = TREE_INT_CST_LOW (TREE_VALUE (TREE_VALUE(list))); |
| if (location >= 0 |
| && location <= 0x1000000) |
| return 'i'; |
| } |
| return 'I'; |
| } |
| if (is_attribute_p ("cb", TREE_PURPOSE (list))) |
| return 'c'; |
| list = TREE_CHAIN (list); |
| } |
| if (TARGET_TF |
| && TREE_CODE (decl) == FUNCTION_DECL |
| && DECL_SECTION_NAME (decl) == 0) |
| return 'f'; |
| return 0; |
| } |
| |
| static int |
| mep_comp_type_attributes (const_tree t1, const_tree t2) |
| { |
| int vliw1, vliw2; |
| |
| vliw1 = (lookup_attribute ("vliw", TYPE_ATTRIBUTES (t1)) != 0); |
| vliw2 = (lookup_attribute ("vliw", TYPE_ATTRIBUTES (t2)) != 0); |
| |
| if (vliw1 != vliw2) |
| return 0; |
| |
| return 1; |
| } |
| |
| static void |
| mep_insert_attributes (tree decl, tree *attributes) |
| { |
| int size; |
| const char *secname = 0; |
| tree attrib, attrlist; |
| char encoding; |
| |
| if (TREE_CODE (decl) == FUNCTION_DECL) |
| { |
| const char *funcname = IDENTIFIER_POINTER (DECL_NAME (decl)); |
| |
| if (mep_lookup_pragma_disinterrupt (funcname)) |
| { |
| attrib = build_tree_list (get_identifier ("disinterrupt"), NULL_TREE); |
| *attributes = chainon (*attributes, attrib); |
| } |
| } |
| |
| if (TREE_CODE (decl) != VAR_DECL |
| || ! (TREE_PUBLIC (decl) || TREE_STATIC (decl) || DECL_EXTERNAL (decl))) |
| return; |
| |
| if (TREE_READONLY (decl) && TARGET_DC) |
| /* -mdc means that const variables default to the near section, |
| regardless of the size cutoff. */ |
| return; |
| |
| /* User specified an attribute, so override the default. |
| Ignore storage attribute of pointed to variable. char __far * x; */ |
| if (! (TREE_TYPE (decl) && TREE_CODE (TREE_TYPE (decl)) == POINTER_TYPE)) |
| { |
| if (TYPE_P (decl) && TYPE_ATTRIBUTES (decl) && *attributes) |
| TYPE_ATTRIBUTES (decl) = NULL_TREE; |
| else if (DECL_ATTRIBUTES (decl) && *attributes) |
| DECL_ATTRIBUTES (decl) = NULL_TREE; |
| } |
| |
| attrlist = *attributes ? *attributes : DECL_ATTRIBUTES (decl); |
| encoding = mep_attrlist_to_encoding (attrlist, decl); |
| if (!encoding && TYPE_P (TREE_TYPE (decl))) |
| { |
| attrlist = TYPE_ATTRIBUTES (TREE_TYPE (decl)); |
| encoding = mep_attrlist_to_encoding (attrlist, decl); |
| } |
| if (encoding) |
| { |
| /* This means that the declaration has a specific section |
| attribute, so we should not apply the default rules. */ |
| |
| if (encoding == 'i' || encoding == 'I') |
| { |
| tree attr = lookup_attribute ("io", attrlist); |
| if (attr |
| && TREE_VALUE (attr) |
| && TREE_VALUE (TREE_VALUE(attr))) |
| { |
| int location = TREE_INT_CST_LOW (TREE_VALUE (TREE_VALUE(attr))); |
| static tree previous_value = 0; |
| static int previous_location = 0; |
| static tree previous_name = 0; |
| |
| /* We take advantage of the fact that gcc will reuse the |
| same tree pointer when applying an attribute to a |
| list of decls, but produce a new tree for attributes |
| on separate source lines, even when they're textually |
| identical. This is the behavior we want. */ |
| if (TREE_VALUE (attr) == previous_value |
| && location == previous_location) |
| { |
| warning(0, "__io address 0x%x is the same for %qE and %qE", |
| location, previous_name, DECL_NAME (decl)); |
| } |
| previous_name = DECL_NAME (decl); |
| previous_location = location; |
| previous_value = TREE_VALUE (attr); |
| } |
| } |
| return; |
| } |
| |
| |
| /* Declarations of arrays can change size. Don't trust them. */ |
| if (TREE_CODE (TREE_TYPE (decl)) == ARRAY_TYPE) |
| size = 0; |
| else |
| size = int_size_in_bytes (TREE_TYPE (decl)); |
| |
| if (TARGET_RAND_TPGP && size <= 4 && size > 0) |
| { |
| if (TREE_PUBLIC (decl) |
| || DECL_EXTERNAL (decl) |
| || TREE_STATIC (decl)) |
| { |
| const char *name = IDENTIFIER_POINTER (DECL_NAME (decl)); |
| int key = 0; |
| |
| while (*name) |
| key += *name++; |
| |
| switch (key & 3) |
| { |
| case 0: |
| secname = "based"; |
| break; |
| case 1: |
| secname = "tiny"; |
| break; |
| case 2: |
| secname = "far"; |
| break; |
| default: |
| ; |
| } |
| } |
| } |
| else |
| { |
| if (size <= mep_based_cutoff && size > 0) |
| secname = "based"; |
| else if (size <= mep_tiny_cutoff && size > 0) |
| secname = "tiny"; |
| else if (TARGET_L) |
| secname = "far"; |
| } |
| |
| if (mep_const_section && TREE_READONLY (decl)) |
| { |
| if (strcmp (mep_const_section, "tiny") == 0) |
| secname = "tiny"; |
| else if (strcmp (mep_const_section, "near") == 0) |
| return; |
| else if (strcmp (mep_const_section, "far") == 0) |
| secname = "far"; |
| } |
| |
| if (!secname) |
| return; |
| |
| if (!mep_multiple_address_regions (*attributes, true) |
| && !mep_multiple_address_regions (DECL_ATTRIBUTES (decl), false)) |
| { |
| attrib = build_tree_list (get_identifier (secname), NULL_TREE); |
| |
| /* Chain the attribute directly onto the variable's DECL_ATTRIBUTES |
| in order to avoid the POINTER_TYPE bypasses in mep_validate_near_far |
| and mep_validate_based_tiny. */ |
| DECL_ATTRIBUTES (decl) = chainon (DECL_ATTRIBUTES (decl), attrib); |
| } |
| } |
| |
| static void |
| mep_encode_section_info (tree decl, rtx rtl, int first) |
| { |
| rtx rtlname; |
| const char *oldname; |
| const char *secname; |
| char encoding; |
| char *newname; |
| tree idp; |
| int maxsize; |
| tree type; |
| tree mep_attributes; |
| |
| if (! first) |
| return; |
| |
| if (TREE_CODE (decl) != VAR_DECL |
| && TREE_CODE (decl) != FUNCTION_DECL) |
| return; |
| |
| rtlname = XEXP (rtl, 0); |
| if (GET_CODE (rtlname) == SYMBOL_REF) |
| oldname = XSTR (rtlname, 0); |
| else if (GET_CODE (rtlname) == MEM |
| && GET_CODE (XEXP (rtlname, 0)) == SYMBOL_REF) |
| oldname = XSTR (XEXP (rtlname, 0), 0); |
| else |
| gcc_unreachable (); |
| |
| type = TREE_TYPE (decl); |
| if (type == error_mark_node) |
| return; |
| mep_attributes = MEP_ATTRIBUTES (decl); |
| |
| encoding = mep_attrlist_to_encoding (mep_attributes, decl); |
| |
| if (encoding) |
| { |
| newname = (char *) alloca (strlen (oldname) + 4); |
| sprintf (newname, "@%c.%s", encoding, oldname); |
| idp = get_identifier (newname); |
| XEXP (rtl, 0) = |
| gen_rtx_SYMBOL_REF (Pmode, IDENTIFIER_POINTER (idp)); |
| SYMBOL_REF_WEAK (XEXP (rtl, 0)) = DECL_WEAK (decl); |
| SET_SYMBOL_REF_DECL (XEXP (rtl, 0), decl); |
| |
| switch (encoding) |
| { |
| case 'b': |
| maxsize = 128; |
| secname = "based"; |
| break; |
| case 't': |
| maxsize = 65536; |
| secname = "tiny"; |
| break; |
| case 'n': |
| maxsize = 0x1000000; |
| secname = "near"; |
| break; |
| default: |
| maxsize = 0; |
| secname = 0; |
| break; |
| } |
| if (maxsize && int_size_in_bytes (TREE_TYPE (decl)) > maxsize) |
| { |
| warning (0, "variable %s (%ld bytes) is too large for the %s section (%d bytes)", |
| oldname, |
| (long) int_size_in_bytes (TREE_TYPE (decl)), |
| secname, |
| maxsize); |
| } |
| } |
| } |
| |
| const char * |
| mep_strip_name_encoding (const char *sym) |
| { |
| while (1) |
| { |
| if (*sym == '*') |
| sym++; |
| else if (*sym == '@' && sym[2] == '.') |
| sym += 3; |
| else |
| return sym; |
| } |
| } |
| |
| static section * |
| mep_select_section (tree decl, int reloc ATTRIBUTE_UNUSED, |
| unsigned HOST_WIDE_INT align ATTRIBUTE_UNUSED) |
| { |
| int readonly = 1; |
| int encoding; |
| |
| switch (TREE_CODE (decl)) |
| { |
| case VAR_DECL: |
| if (!TREE_READONLY (decl) |
| || TREE_SIDE_EFFECTS (decl) |
| || !DECL_INITIAL (decl) |
| || (DECL_INITIAL (decl) != error_mark_node |
| && !TREE_CONSTANT (DECL_INITIAL (decl)))) |
| readonly = 0; |
| break; |
| case CONSTRUCTOR: |
| if (! TREE_CONSTANT (decl)) |
| readonly = 0; |
| break; |
| |
| default: |
| break; |
| } |
| |
| if (TREE_CODE (decl) == FUNCTION_DECL) |
| { |
| const char *name = XSTR (XEXP (DECL_RTL (decl), 0), 0); |
| |
| if (name[0] == '@' && name[2] == '.') |
| encoding = name[1]; |
| else |
| encoding = 0; |
| |
| if (flag_function_sections || DECL_ONE_ONLY (decl)) |
| mep_unique_section (decl, 0); |
| else if (lookup_attribute ("vliw", TYPE_ATTRIBUTES (TREE_TYPE (decl)))) |
| { |
| if (encoding == 'f') |
| return vftext_section; |
| else |
| return vtext_section; |
| } |
| else if (encoding == 'f') |
| return ftext_section; |
| else |
| return text_section; |
| } |
| |
| if (TREE_CODE (decl) == VAR_DECL) |
| { |
| const char *name = XSTR (XEXP (DECL_RTL (decl), 0), 0); |
| |
| if (name[0] == '@' && name[2] == '.') |
| switch (name[1]) |
| { |
| case 'b': |
| return based_section; |
| |
| case 't': |
| if (readonly) |
| return srodata_section; |
| if (DECL_INITIAL (decl)) |
| return sdata_section; |
| return tinybss_section; |
| |
| case 'f': |
| if (readonly) |
| return frodata_section; |
| return far_section; |
| |
| case 'i': |
| case 'I': |
| error_at (DECL_SOURCE_LOCATION (decl), |
| "variable %D of type %<io%> must be uninitialized", decl); |
| return data_section; |
| |
| case 'c': |
| error_at (DECL_SOURCE_LOCATION (decl), |
| "variable %D of type %<cb%> must be uninitialized", decl); |
| return data_section; |
| } |
| } |
| |
| if (readonly) |
| return readonly_data_section; |
| |
| return data_section; |
| } |
| |
| static void |
| mep_unique_section (tree decl, int reloc) |
| { |
| static const char *prefixes[][2] = |
| { |
| { ".text.", ".gnu.linkonce.t." }, |
| { ".rodata.", ".gnu.linkonce.r." }, |
| { ".data.", ".gnu.linkonce.d." }, |
| { ".based.", ".gnu.linkonce.based." }, |
| { ".sdata.", ".gnu.linkonce.s." }, |
| { ".far.", ".gnu.linkonce.far." }, |
| { ".ftext.", ".gnu.linkonce.ft." }, |
| { ".frodata.", ".gnu.linkonce.frd." }, |
| { ".srodata.", ".gnu.linkonce.srd." }, |
| { ".vtext.", ".gnu.linkonce.v." }, |
| { ".vftext.", ".gnu.linkonce.vf." } |
| }; |
| int sec = 2; /* .data */ |
| int len; |
| const char *name, *prefix; |
| char *string; |
| |
| name = IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (decl)); |
| if (DECL_RTL (decl)) |
| name = XSTR (XEXP (DECL_RTL (decl), 0), 0); |
| |
| if (TREE_CODE (decl) == FUNCTION_DECL) |
| { |
| if (lookup_attribute ("vliw", TYPE_ATTRIBUTES (TREE_TYPE (decl)))) |
| sec = 9; /* .vtext */ |
| else |
| sec = 0; /* .text */ |
| } |
| else if (decl_readonly_section (decl, reloc)) |
| sec = 1; /* .rodata */ |
| |
| if (name[0] == '@' && name[2] == '.') |
| { |
| switch (name[1]) |
| { |
| case 'b': |
| sec = 3; /* .based */ |
| break; |
| case 't': |
| if (sec == 1) |
| sec = 8; /* .srodata */ |
| else |
| sec = 4; /* .sdata */ |
| break; |
| case 'f': |
| if (sec == 0) |
| sec = 6; /* .ftext */ |
| else if (sec == 9) |
| sec = 10; /* .vftext */ |
| else if (sec == 1) |
| sec = 7; /* .frodata */ |
| else |
| sec = 5; /* .far. */ |
| break; |
| } |
| name += 3; |
| } |
| |
| prefix = prefixes[sec][DECL_ONE_ONLY(decl)]; |
| len = strlen (name) + strlen (prefix); |
| string = (char *) alloca (len + 1); |
| |
| sprintf (string, "%s%s", prefix, name); |
| |
| DECL_SECTION_NAME (decl) = build_string (len, string); |
| } |
| |
| /* Given a decl, a section name, and whether the decl initializer |
| has relocs, choose attributes for the section. */ |
| |
| #define SECTION_MEP_VLIW SECTION_MACH_DEP |
| |
| static unsigned int |
| mep_section_type_flags (tree decl, const char *name, int reloc) |
| { |
| unsigned int flags = default_section_type_flags (decl, name, reloc); |
| |
| if (decl && TREE_CODE (decl) == FUNCTION_DECL |
| && lookup_attribute ("vliw", TYPE_ATTRIBUTES (TREE_TYPE (decl)))) |
| flags |= SECTION_MEP_VLIW; |
| |
| return flags; |
| } |
| |
| /* Switch to an arbitrary section NAME with attributes as specified |
| by FLAGS. ALIGN specifies any known alignment requirements for |
| the section; 0 if the default should be used. |
| |
| Differs from the standard ELF version only in support of VLIW mode. */ |
| |
| static void |
| mep_asm_named_section (const char *name, unsigned int flags, tree decl ATTRIBUTE_UNUSED) |
| { |
| char flagchars[8], *f = flagchars; |
| const char *type; |
| |
| if (!(flags & SECTION_DEBUG)) |
| *f++ = 'a'; |
| if (flags & SECTION_WRITE) |
| *f++ = 'w'; |
| if (flags & SECTION_CODE) |
| *f++ = 'x'; |
| if (flags & SECTION_SMALL) |
| *f++ = 's'; |
| if (flags & SECTION_MEP_VLIW) |
| *f++ = 'v'; |
| *f = '\0'; |
| |
| if (flags & SECTION_BSS) |
| type = "nobits"; |
| else |
| type = "progbits"; |
| |
| fprintf (asm_out_file, "\t.section\t%s,\"%s\",@%s\n", |
| name, flagchars, type); |
| |
| if (flags & SECTION_CODE) |
| fputs ((flags & SECTION_MEP_VLIW ? "\t.vliw\n" : "\t.core\n"), |
| asm_out_file); |
| } |
| |
| void |
| mep_output_aligned_common (FILE *stream, tree decl, const char *name, |
| int size, int align, int global) |
| { |
| /* We intentionally don't use mep_section_tag() here. */ |
| if (name[0] == '@' |
| && (name[1] == 'i' || name[1] == 'I' || name[1] == 'c') |
| && name[2] == '.') |
| { |
| int location = -1; |
| tree attr = lookup_attribute ((name[1] == 'c' ? "cb" : "io"), |
| DECL_ATTRIBUTES (decl)); |
| if (attr |
| && TREE_VALUE (attr) |
| && TREE_VALUE (TREE_VALUE(attr))) |
| location = TREE_INT_CST_LOW (TREE_VALUE (TREE_VALUE(attr))); |
| if (location == -1) |
| return; |
| if (global) |
| { |
| fprintf (stream, "\t.globl\t"); |
| assemble_name (stream, name); |
| fprintf (stream, "\n"); |
| } |
| assemble_name (stream, name); |
| fprintf (stream, " = %d\n", location); |
| return; |
| } |
| if (name[0] == '@' && name[2] == '.') |
| { |
| const char *sec = 0; |
| switch (name[1]) |
| { |
| case 'b': |
| switch_to_section (based_section); |
| sec = ".based"; |
| break; |
| case 't': |
| switch_to_section (tinybss_section); |
| sec = ".sbss"; |
| break; |
| case 'f': |
| switch_to_section (farbss_section); |
| sec = ".farbss"; |
| break; |
| } |
| if (sec) |
| { |
| const char *name2; |
| int p2align = 0; |
| |
| while (align > BITS_PER_UNIT) |
| { |
| align /= 2; |
| p2align ++; |
| } |
| name2 = targetm.strip_name_encoding (name); |
| if (global) |
| fprintf (stream, "\t.globl\t%s\n", name2); |
| fprintf (stream, "\t.p2align %d\n", p2align); |
| fprintf (stream, "\t.type\t%s,@object\n", name2); |
| fprintf (stream, "\t.size\t%s,%d\n", name2, size); |
| fprintf (stream, "%s:\n\t.zero\t%d\n", name2, size); |
| return; |
| } |
| } |
| |
| if (!global) |
| { |
| fprintf (stream, "\t.local\t"); |
| assemble_name (stream, name); |
| fprintf (stream, "\n"); |
| } |
| fprintf (stream, "\t.comm\t"); |
| assemble_name (stream, name); |
| fprintf (stream, ",%u,%u\n", size, align / BITS_PER_UNIT); |
| } |
| |
| /* Trampolines. */ |
| |
| static void |
| mep_trampoline_init (rtx m_tramp, tree fndecl, rtx static_chain) |
| { |
| rtx addr = XEXP (m_tramp, 0); |
| rtx fnaddr = XEXP (DECL_RTL (fndecl), 0); |
| |
| emit_library_call (gen_rtx_SYMBOL_REF (Pmode, "__mep_trampoline_helper"), |
| LCT_NORMAL, VOIDmode, 3, |
| addr, Pmode, |
| fnaddr, Pmode, |
| static_chain, Pmode); |
| } |
| |
| /* Experimental Reorg. */ |
| |
| static bool |
| mep_mentioned_p (rtx in, |
| rtx reg, /* NULL for mem */ |
| int modes_too) /* if nonzero, modes must match also. */ |
| { |
| const char *fmt; |
| int i; |
| enum rtx_code code; |
| |
| if (in == 0) |
| return false; |
| if (reg && GET_CODE (reg) != REG) |
| return false; |
| |
| if (GET_CODE (in) == LABEL_REF) |
| return (reg == 0); |
| |
| code = GET_CODE (in); |
| |
| switch (code) |
| { |
| case MEM: |
| if (reg) |
| return mep_mentioned_p (XEXP (in, 0), reg, modes_too); |
| return true; |
| |
| case REG: |
| if (!reg) |
| return false; |
| if (modes_too && (GET_MODE (in) != GET_MODE (reg))) |
| return false; |
| return (REGNO (in) == REGNO (reg)); |
| |
| case SCRATCH: |
| case CC0: |
| case PC: |
| case CONST_INT: |
| case CONST_DOUBLE: |
| return false; |
| |
| default: |
| break; |
| } |
| |
| /* Set's source should be read-only. */ |
| if (code == SET && !reg) |
| return mep_mentioned_p (SET_DEST (in), reg, modes_too); |
| |
| fmt = GET_RTX_FORMAT (code); |
| |
| for (i = GET_RTX_LENGTH (code) - 1; i >= 0; i--) |
| { |
| if (fmt[i] == 'E') |
| { |
| register int j; |
| for (j = XVECLEN (in, i) - 1; j >= 0; j--) |
| if (mep_mentioned_p (XVECEXP (in, i, j), reg, modes_too)) |
| return true; |
| } |
| else if (fmt[i] == 'e' |
| && mep_mentioned_p (XEXP (in, i), reg, modes_too)) |
| return true; |
| } |
| return false; |
| } |
| |
| #define EXPERIMENTAL_REGMOVE_REORG 1 |
| |
| #if EXPERIMENTAL_REGMOVE_REORG |
| |
| static int |
| mep_compatible_reg_class (int r1, int r2) |
| { |
| if (GR_REGNO_P (r1) && GR_REGNO_P (r2)) |
| return 1; |
| if (CR_REGNO_P (r1) && CR_REGNO_P (r2)) |
| return 1; |
| return 0; |
| } |
| |
| static void |
| mep_reorg_regmove (rtx insns) |
| { |
| rtx insn, next, pat, follow, *where; |
| int count = 0, done = 0, replace, before = 0; |
| |
| if (dump_file) |
| for (insn = insns; insn; insn = NEXT_INSN (insn)) |
| if (GET_CODE (insn) == INSN) |
| before++; |
| |
| /* We're looking for (set r2 r1) moves where r1 dies, followed by a |
| set that uses the r2 and r2 dies there. We replace r2 with r1 |
| and see if it's still a valid insn. If so, delete the first set. |
| Copied from reorg.c. */ |
| |
| while (!done) |
| { |
| done = 1; |
| for (insn = insns; insn; insn = next) |
| { |
| next = next_nonnote_nondebug_insn (insn); |
| if (GET_CODE (insn) != INSN) |
| continue; |
| pat = PATTERN (insn); |
| |
| replace = 0; |
| |
| if (GET_CODE (pat) == SET |
| && GET_CODE (SET_SRC (pat)) == REG |
| && GET_CODE (SET_DEST (pat)) == REG |
| && find_regno_note (insn, REG_DEAD, REGNO (SET_SRC (pat))) |
| && mep_compatible_reg_class (REGNO (SET_SRC (pat)), REGNO (SET_DEST (pat)))) |
| { |
| follow = next_nonnote_nondebug_insn (insn); |
| if (dump_file) |
| fprintf (dump_file, "superfluous moves: considering %d\n", INSN_UID (insn)); |
| |
| while (follow && GET_CODE (follow) == INSN |
| && GET_CODE (PATTERN (follow)) == SET |
| && !dead_or_set_p (follow, SET_SRC (pat)) |
| && !mep_mentioned_p (PATTERN (follow), SET_SRC (pat), 0) |
| && !mep_mentioned_p (PATTERN (follow), SET_DEST (pat), 0)) |
| { |
| if (dump_file) |
| fprintf (dump_file, "\tskipping %d\n", INSN_UID (follow)); |
| follow = next_nonnote_insn (follow); |
| } |
| |
| if (dump_file) |
| fprintf (dump_file, "\tfollow is %d\n", INSN_UID (follow)); |
| if (follow && GET_CODE (follow) == INSN |
| && GET_CODE (PATTERN (follow)) == SET |
| && find_regno_note (follow, REG_DEAD, REGNO (SET_DEST (pat)))) |
| { |
| if (GET_CODE (SET_DEST (PATTERN (follow))) == REG) |
| { |
| if (mep_mentioned_p (SET_SRC (PATTERN (follow)), SET_DEST (pat), 1)) |
| { |
| replace = 1; |
| where = & SET_SRC (PATTERN (follow)); |
| } |
| } |
| else if (GET_CODE (SET_DEST (PATTERN (follow))) == MEM) |
| { |
| if (mep_mentioned_p (PATTERN (follow), SET_DEST (pat), 1)) |
| { |
| replace = 1; |
| where = & PATTERN (follow); |
| } |
| } |
| } |
| } |
| |
| /* If so, follow is the corresponding insn */ |
| if (replace) |
| { |
| if (dump_file) |
| { |
| rtx x; |
| |
| fprintf (dump_file, "----- Candidate for superfluous move deletion:\n\n"); |
| for (x = insn; x ;x = NEXT_INSN (x)) |
| { |
| print_rtl_single (dump_file, x); |
| if (x == follow) |
| break; |
| fprintf (dump_file, "\n"); |
| } |
| } |
| |
| if (validate_replace_rtx_subexp (SET_DEST (pat), SET_SRC (pat), |
| follow, where)) |
| { |
| count ++; |
| delete_insn (insn); |
| if (dump_file) |
| { |
| fprintf (dump_file, "\n----- Success! new insn:\n\n"); |
| print_rtl_single (dump_file, follow); |
| } |
| done = 0; |
| } |
| } |
| } |
| } |
| |
| if (dump_file) |
| { |
| fprintf (dump_file, "\n%d insn%s deleted out of %d.\n\n", count, count == 1 ? "" : "s", before); |
| fprintf (dump_file, "=====\n"); |
| } |
| } |
| #endif |
| |
| |
| /* Figure out where to put LABEL, which is the label for a repeat loop. |
| If INCLUDING, LAST_INSN is the last instruction in the loop, otherwise |
| the loop ends just before LAST_INSN. If SHARED, insns other than the |
| "repeat" might use LABEL to jump to the loop's continuation point. |
| |
| Return the last instruction in the adjusted loop. */ |
| |
| static rtx |
| mep_insert_repeat_label_last (rtx last_insn, rtx label, bool including, |
| bool shared) |
| { |
| rtx next, prev; |
| int count = 0, code, icode; |
| |
| if (dump_file) |
| fprintf (dump_file, "considering end of repeat loop at insn %d\n", |
| INSN_UID (last_insn)); |
| |
| /* Set PREV to the last insn in the loop. */ |
| prev = last_insn; |
| if (!including) |
| prev = PREV_INSN (prev); |
| |
| /* Set NEXT to the next insn after the repeat label. */ |
| next = last_insn; |
| if (!shared) |
| while (prev != 0) |
| { |
| code = GET_CODE (prev); |
| if (code == CALL_INSN || code == CODE_LABEL || code == BARRIER) |
| break; |
| |
| if (INSN_P (prev)) |
| { |
| if (GET_CODE (PATTERN (prev)) == SEQUENCE) |
| prev = XVECEXP (PATTERN (prev), 0, 1); |
| |
| /* Other insns that should not be in the last two opcodes. */ |
| icode = recog_memoized (prev); |
| if (icode < 0 |
| || icode == CODE_FOR_repeat |
| || icode == CODE_FOR_erepeat |
| || get_attr_may_trap (prev) == MAY_TRAP_YES) |
| break; |
| |
| /* That leaves JUMP_INSN and INSN. It will have BImode if it |
| is the second instruction in a VLIW bundle. In that case, |
| loop again: if the first instruction also satisfies the |
| conditions above then we will reach here again and put |
| both of them into the repeat epilogue. Otherwise both |
| should remain outside. */ |
| if (GET_MODE (prev) != BImode) |
| { |
| count++; |
| next = prev; |
| if (dump_file) |
| print_rtl_single (dump_file, next); |
| if (count == 2) |
| break; |
| } |
| } |
| prev = PREV_INSN (prev); |
| } |
| |
| /* See if we're adding the label immediately after the repeat insn. |
| If so, we need to separate them with a nop. */ |
| prev = prev_real_insn (next); |
| if (prev) |
| switch (recog_memoized (prev)) |
| { |
| case CODE_FOR_repeat: |
| case CODE_FOR_erepeat: |
| if (dump_file) |
| fprintf (dump_file, "Adding nop inside loop\n"); |
| emit_insn_before (gen_nop (), next); |
| break; |
| |
| default: |
| break; |
| } |
| |
| /* Insert the label. */ |
| emit_label_before (label, next); |
| |
| /* Insert the nops. */ |
| if (dump_file && count < 2) |
| fprintf (dump_file, "Adding %d nop%s\n\n", |
| 2 - count, count == 1 ? "" : "s"); |
| |
| for (; count < 2; count++) |
| if (including) |
| last_insn = emit_insn_after (gen_nop (), last_insn); |
| else |
| emit_insn_before (gen_nop (), last_insn); |
| |
| return last_insn; |
| } |
| |
| |
| void |
| mep_emit_doloop (rtx *operands, int is_end) |
| { |
| rtx tag; |
| |
| if (cfun->machine->doloop_tags == 0 |
| || cfun->machine->doloop_tag_from_end == is_end) |
| { |
| cfun->machine->doloop_tags++; |
| cfun->machine->doloop_tag_from_end = is_end; |
| } |
| |
| tag = GEN_INT (cfun->machine->doloop_tags - 1); |
| if (is_end) |
| emit_jump_insn (gen_doloop_end_internal (operands[0], operands[4], tag)); |
| else |
| emit_insn (gen_doloop_begin_internal (operands[0], operands[0], tag)); |
| } |
| |
| |
| /* Code for converting doloop_begins and doloop_ends into valid |
| MeP instructions. A doloop_begin is just a placeholder: |
| |
| $count = unspec ($count) |
| |
| where $count is initially the number of iterations - 1. |
| doloop_end has the form: |
| |
| if ($count-- == 0) goto label |
| |
| The counter variable is private to the doloop insns, nothing else |
| relies on its value. |
| |
| There are three cases, in decreasing order of preference: |
| |
| 1. A loop has exactly one doloop_begin and one doloop_end. |
| The doloop_end branches to the first instruction after |
| the doloop_begin. |
| |
| In this case we can replace the doloop_begin with a repeat |
| instruction and remove the doloop_end. I.e.: |
| |
| $count1 = unspec ($count1) |
| label: |
| ... |
| insn1 |
| insn2 |
| if ($count2-- == 0) goto label |
| |
| becomes: |
| |
| repeat $count1,repeat_label |
| label: |
| ... |
| repeat_label: |
| insn1 |
| insn2 |
| # end repeat |
| |
| 2. As for (1), except there are several doloop_ends. One of them |
| (call it X) falls through to a label L. All the others fall |
| through to branches to L. |
| |
| In this case, we remove X and replace the other doloop_ends |
| with branches to the repeat label. For example: |
| |
| $count1 = unspec ($count1) |
| start: |
| ... |
| if ($count2-- == 0) goto label |
| end: |
| ... |
| if ($count3-- == 0) goto label |
| goto end |
| |
| becomes: |
| |
| repeat $count1,repeat_label |
| start: |
| ... |
| repeat_label: |
| nop |
| nop |
| # end repeat |
| end: |
| ... |
| goto repeat_label |
| |
| 3. The fallback case. Replace doloop_begins with: |
| |
| $count = $count + 1 |
| |
| Replace doloop_ends with the equivalent of: |
| |
| $count = $count - 1 |
| if ($count == 0) goto label |
| |
| Note that this might need a scratch register if $count |
| is stored in memory. */ |
| |
| /* A structure describing one doloop_begin. */ |
| struct mep_doloop_begin { |
| /* The next doloop_begin with the same tag. */ |
| struct mep_doloop_begin *next; |
| |
| /* The instruction itself. */ |
| rtx insn; |
| |
| /* The initial counter value. This is known to be a general register. */ |
| rtx counter; |
| }; |
| |
| /* A structure describing a doloop_end. */ |
| struct mep_doloop_end { |
| /* The next doloop_end with the same loop tag. */ |
| struct mep_doloop_end *next; |
| |
| /* The instruction itself. */ |
| rtx insn; |
| |
| /* The first instruction after INSN when the branch isn't taken. */ |
| rtx fallthrough; |
| |
| /* The location of the counter value. Since doloop_end_internal is a |
| jump instruction, it has to allow the counter to be stored anywhere |
| (any non-fixed register or memory location). */ |
| rtx counter; |
| |
| /* The target label (the place where the insn branches when the counter |
| isn't zero). */ |
| rtx label; |
| |
| /* A scratch register. Only available when COUNTER isn't stored |
| in a general register. */ |
| rtx scratch; |
| }; |
| |
| |
| /* One do-while loop. */ |
| struct mep_doloop { |
| /* All the doloop_begins for this loop (in no particular order). */ |
| struct mep_doloop_begin *begin; |
| |
| /* All the doloop_ends. When there is more than one, arrange things |
| so that the first one is the most likely to be X in case (2) above. */ |
| struct mep_doloop_end *end; |
| }; |
| |
| |
| /* Return true if LOOP can be converted into repeat/repeat_end form |
| (that is, if it matches cases (1) or (2) above). */ |
| |
| static bool |
| mep_repeat_loop_p (struct mep_doloop *loop) |
| { |
| struct mep_doloop_end *end; |
| rtx fallthrough; |
| |
| /* There must be exactly one doloop_begin and at least one doloop_end. */ |
| if (loop->begin == 0 || loop->end == 0 || loop->begin->next != 0) |
| return false; |
| |
| /* The first doloop_end (X) must branch back to the insn after |
| the doloop_begin. */ |
| if (prev_real_insn (loop->end->label) != loop->begin->insn) |
| return false; |
| |
| /* All the other doloop_ends must branch to the same place as X. |
| When the branch isn't taken, they must jump to the instruction |
| after X. */ |
| fallthrough = loop->end->fallthrough; |
| for (end = loop->end->next; end != 0; end = end->next) |
| if (end->label != loop->end->label |
| || !simplejump_p (end->fallthrough) |
| || next_real_insn (JUMP_LABEL (end->fallthrough)) != fallthrough) |
| return false; |
| |
| return true; |
| } |
| |
| |
| /* The main repeat reorg function. See comment above for details. */ |
| |
| static void |
| mep_reorg_repeat (rtx insns) |
| { |
| rtx insn; |
| struct mep_doloop *loops, *loop; |
| struct mep_doloop_begin *begin; |
| struct mep_doloop_end *end; |
| |
| /* Quick exit if we haven't created any loops. */ |
| if (cfun->machine->doloop_tags == 0) |
| return; |
| |
| /* Create an array of mep_doloop structures. */ |
| loops = (struct mep_doloop *) alloca (sizeof (loops[0]) * cfun->machine->doloop_tags); |
| memset (loops, 0, sizeof (loops[0]) * cfun->machine->doloop_tags); |
| |
| /* Search the function for do-while insns and group them by loop tag. */ |
| for (insn = insns; insn; insn = NEXT_INSN (insn)) |
| if (INSN_P (insn)) |
| switch (recog_memoized (insn)) |
| { |
| case CODE_FOR_doloop_begin_internal: |
| insn_extract (insn); |
| loop = &loops[INTVAL (recog_data.operand[2])]; |
| |
| begin = (struct mep_doloop_begin *) alloca (sizeof (struct mep_doloop_begin)); |
| begin->next = loop->begin; |
| begin->insn = insn; |
| begin->counter = recog_data.operand[0]; |
| |
| loop->begin = begin; |
| break; |
| |
| case CODE_FOR_doloop_end_internal: |
| insn_extract (insn); |
| loop = &loops[INTVAL (recog_data.operand[2])]; |
| |
| end = (struct mep_doloop_end *) alloca (sizeof (struct mep_doloop_end)); |
| end->insn = insn; |
| end->fallthrough = next_real_insn (insn); |
| end->counter = recog_data.operand[0]; |
| end->label = recog_data.operand[1]; |
| end->scratch = recog_data.operand[3]; |
| |
| /* If this insn falls through to an unconditional jump, |
| give it a lower priority than the others. */ |
| if (loop->end != 0 && simplejump_p (end->fallthrough)) |
| { |
| end->next = loop->end->next; |
| loop->end->next = end; |
| } |
| else |
| { |
| end->next = loop->end; |
| loop->end = end; |
| } |
| break; |
| } |
| |
| /* Convert the insns for each loop in turn. */ |
| for (loop = loops; loop < loops + cfun->machine->doloop_tags; loop++) |
| if (mep_repeat_loop_p (loop)) |
| { |
| /* Case (1) or (2). */ |
| rtx repeat_label, label_ref; |
| |
| /* Create a new label for the repeat insn. */ |
| repeat_label = gen_label_rtx (); |
| |
| /* Replace the doloop_begin with a repeat. */ |
| label_ref = gen_rtx_LABEL_REF (VOIDmode, repeat_label); |
| emit_insn_before (gen_repeat (loop->begin->counter, label_ref), |
| loop->begin->insn); |
| delete_insn (loop->begin->insn); |
| |
| /* Insert the repeat label before the first doloop_end. |
| Fill the gap with nops if there are other doloop_ends. */ |
| mep_insert_repeat_label_last (loop->end->insn, repeat_label, |
| false, loop->end->next != 0); |
| |
| /* Emit a repeat_end (to improve the readability of the output). */ |
| emit_insn_before (gen_repeat_end (), loop->end->insn); |
| |
| /* Delete the first doloop_end. */ |
| delete_insn (loop->end->insn); |
| |
| /* Replace the others with branches to REPEAT_LABEL. */ |
| for (end = loop->end->next; end != 0; end = end->next) |
| { |
| emit_jump_insn_before (gen_jump (repeat_label), end->insn); |
| delete_insn (end->insn); |
| delete_insn (end->fallthrough); |
| } |
| } |
| else |
| { |
| /* Case (3). First replace all the doloop_begins with increment |
| instructions. */ |
| for (begin = loop->begin; begin != 0; begin = begin->next) |
| { |
| emit_insn_before (gen_add3_insn (copy_rtx (begin->counter), |
| begin->counter, const1_rtx), |
| begin->insn); |
| delete_insn (begin->insn); |
| } |
| |
| /* Replace all the doloop_ends with decrement-and-branch sequences. */ |
| for (end = loop->end; end != 0; end = end->next) |
| { |
| rtx reg; |
| |
| start_sequence (); |
| |
| /* Load the counter value into a general register. */ |
| reg = end->counter; |
| if (!REG_P (reg) || REGNO (reg) > 15) |
| { |
| reg = end->scratch; |
| emit_move_insn (copy_rtx (reg), copy_rtx (end->counter)); |
| } |
| |
| /* Decrement the counter. */ |
| emit_insn (gen_add3_insn (copy_rtx (reg), copy_rtx (reg), |
| constm1_rtx)); |
| |
| /* Copy it back to its original location. */ |
| if (reg != end->counter) |
| emit_move_insn (copy_rtx (end->counter), copy_rtx (reg)); |
| |
| /* Jump back to the start label. */ |
| insn = emit_jump_insn (gen_mep_bne_true (reg, const0_rtx, |
| end->label)); |
| JUMP_LABEL (insn) = end->label; |
| LABEL_NUSES (end->label)++; |
| |
| /* Emit the whole sequence before the doloop_end. */ |
| insn = get_insns (); |
| end_sequence (); |
| emit_insn_before (insn, end->insn); |
| |
| /* Delete the doloop_end. */ |
| delete_insn (end->insn); |
| } |
| } |
| } |
| |
| |
| static bool |
| mep_invertable_branch_p (rtx insn) |
| { |
| rtx cond, set; |
| enum rtx_code old_code; |
| int i; |
| |
| set = PATTERN (insn); |
| if (GET_CODE (set) != SET) |
| return false; |
| if (GET_CODE (XEXP (set, 1)) != IF_THEN_ELSE) |
| return false; |
| cond = XEXP (XEXP (set, 1), 0); |
| old_code = GET_CODE (cond); |
| switch (old_code) |
| { |
| case EQ: |
| PUT_CODE (cond, NE); |
| break; |
| case NE: |
| PUT_CODE (cond, EQ); |
| break; |
| case LT: |
| PUT_CODE (cond, GE); |
| break; |
| case GE: |
| PUT_CODE (cond, LT); |
| break; |
| default: |
| return false; |
| } |
| INSN_CODE (insn) = -1; |
| i = recog_memoized (insn); |
| PUT_CODE (cond, old_code); |
| INSN_CODE (insn) = -1; |
| return i >= 0; |
| } |
| |
| static void |
| mep_invert_branch (rtx insn, rtx after) |
| { |
| rtx cond, set, label; |
| int i; |
| |
| set = PATTERN (insn); |
| |
| gcc_assert (GET_CODE (set) == SET); |
| gcc_assert (GET_CODE (XEXP (set, 1)) == IF_THEN_ELSE); |
| |
| cond = XEXP (XEXP (set, 1), 0); |
| switch (GET_CODE (cond)) |
| { |
| case EQ: |
| PUT_CODE (cond, NE); |
| break; |
| case NE: |
| PUT_CODE (cond, EQ); |
| break; |
| case LT: |
| PUT_CODE (cond, GE); |
| break; |
| case GE: |
| PUT_CODE (cond, LT); |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| label = gen_label_rtx (); |
| emit_label_after (label, after); |
| for (i=1; i<=2; i++) |
| if (GET_CODE (XEXP (XEXP (set, 1), i)) == LABEL_REF) |
| { |
| rtx ref = XEXP (XEXP (set, 1), i); |
| if (LABEL_NUSES (XEXP (ref, 0)) == 1) |
| delete_insn (XEXP (ref, 0)); |
| XEXP (ref, 0) = label; |
| LABEL_NUSES (label) ++; |
| JUMP_LABEL (insn) = label; |
| } |
| INSN_CODE (insn) = -1; |
| i = recog_memoized (insn); |
| gcc_assert (i >= 0); |
| } |
| |
| static void |
| mep_reorg_erepeat (rtx insns) |
| { |
| rtx insn, prev, l, x; |
| int count; |
| |
| for (insn = insns; insn; insn = NEXT_INSN (insn)) |
| if (JUMP_P (insn) |
| && ! JUMP_TABLE_DATA_P (insn) |
| && mep_invertable_branch_p (insn)) |
| { |
| if (dump_file) |
| { |
| fprintf (dump_file, "\n------------------------------\n"); |
| fprintf (dump_file, "erepeat: considering this jump:\n"); |
| print_rtl_single (dump_file, insn); |
| } |
| count = simplejump_p (insn) ? 0 : 1; |
| for (prev = PREV_INSN (insn); prev; prev = PREV_INSN (prev)) |
| { |
| if (GET_CODE (prev) == CALL_INSN |
| || BARRIER_P (prev)) |
| break; |
| |
| if (prev == JUMP_LABEL (insn)) |
| { |
| rtx newlast; |
| if (dump_file) |
| fprintf (dump_file, "found loop top, %d insns\n", count); |
| |
| if (LABEL_NUSES (prev) == 1) |
| /* We're the only user, always safe */ ; |
| else if (LABEL_NUSES (prev) == 2) |
| { |
| /* See if there's a barrier before this label. If |
| so, we know nobody inside the loop uses it. |
| But we must be careful to put the erepeat |
| *after* the label. */ |
| rtx barrier; |
| for (barrier = PREV_INSN (prev); |
| barrier && GET_CODE (barrier) == NOTE; |
| barrier = PREV_INSN (barrier)) |
| ; |
| if (barrier && GET_CODE (barrier) != BARRIER) |
| break; |
| } |
| else |
| { |
| /* We don't know who else, within or without our loop, uses this */ |
| if (dump_file) |
| fprintf (dump_file, "... but there are multiple users, too risky.\n"); |
| break; |
| } |
| |
| /* Generate a label to be used by the erepat insn. */ |
| l = gen_label_rtx (); |
| |
| /* Insert the erepeat after INSN's target label. */ |
| x = gen_erepeat (gen_rtx_LABEL_REF (VOIDmode, l)); |
| LABEL_NUSES (l)++; |
| emit_insn_after (x, prev); |
| |
| /* Insert the erepeat label. */ |
| newlast = (mep_insert_repeat_label_last |
| (insn, l, !simplejump_p (insn), false)); |
| if (simplejump_p (insn)) |
| { |
| emit_insn_before (gen_erepeat_end (), insn); |
| delete_insn (insn); |
| } |
| else |
| { |
| mep_invert_branch (insn, newlast); |
| emit_insn_after (gen_erepeat_end (), newlast); |
| } |
| break; |
| } |
| |
| if (LABEL_P (prev)) |
| { |
| /* A label is OK if there is exactly one user, and we |
| can find that user before the next label. */ |
| rtx user = 0; |
| int safe = 0; |
| if (LABEL_NUSES (prev) == 1) |
| { |
| for (user = PREV_INSN (prev); |
| user && (INSN_P (user) || GET_CODE (user) == NOTE); |
| user = PREV_INSN (user)) |
| if (GET_CODE (user) == JUMP_INSN |
| && JUMP_LABEL (user) == prev) |
| { |
| safe = INSN_UID (user); |
| break; |
| } |
| } |
| if (!safe) |
| break; |
| if (dump_file) |
| fprintf (dump_file, "... ignoring jump from insn %d to %d\n", |
| safe, INSN_UID (prev)); |
| } |
| |
| if (INSN_P (prev)) |
| { |
| count ++; |
| } |
| } |
| } |
| if (dump_file) |
| fprintf (dump_file, "\n==============================\n"); |
| } |
| |
| /* Replace a jump to a return, with a copy of the return. GCC doesn't |
| always do this on its own. */ |
| |
| static void |
| mep_jmp_return_reorg (rtx insns) |
| { |
| rtx insn, label, ret; |
| int ret_code; |
| |
| for (insn = insns; insn; insn = NEXT_INSN (insn)) |
| if (simplejump_p (insn)) |
| { |
| /* Find the fist real insn the jump jumps to. */ |
| label = ret = JUMP_LABEL (insn); |
| while (ret |
| && (GET_CODE (ret) == NOTE |
| || GET_CODE (ret) == CODE_LABEL |
| || GET_CODE (PATTERN (ret)) == USE)) |
| ret = NEXT_INSN (ret); |
| |
| if (ret) |
| { |
| /* Is it a return? */ |
| ret_code = recog_memoized (ret); |
| if (ret_code == CODE_FOR_return_internal |
| || ret_code == CODE_FOR_eh_return_internal) |
| { |
| /* It is. Replace the jump with a return. */ |
| LABEL_NUSES (label) --; |
| if (LABEL_NUSES (label) == 0) |
| delete_insn (label); |
| PATTERN (insn) = copy_rtx (PATTERN (ret)); |
| INSN_CODE (insn) = -1; |
| } |
| } |
| } |
| } |
| |
| |
| static void |
| mep_reorg_addcombine (rtx insns) |
| { |
| rtx i, n; |
| |
| for (i = insns; i; i = NEXT_INSN (i)) |
| if (INSN_P (i) |
| && INSN_CODE (i) == CODE_FOR_addsi3 |
| && GET_CODE (SET_DEST (PATTERN (i))) == REG |
| && GET_CODE (XEXP (SET_SRC (PATTERN (i)), 0)) == REG |
| && REGNO (SET_DEST (PATTERN (i))) == REGNO (XEXP (SET_SRC (PATTERN (i)), 0)) |
| && GET_CODE (XEXP (SET_SRC (PATTERN (i)), 1)) == CONST_INT) |
| { |
| n = NEXT_INSN (i); |
| if (INSN_P (n) |
| && INSN_CODE (n) == CODE_FOR_addsi3 |
| && GET_CODE (SET_DEST (PATTERN (n))) == REG |
| && GET_CODE (XEXP (SET_SRC (PATTERN (n)), 0)) == REG |
| && REGNO (SET_DEST (PATTERN (n))) == REGNO (XEXP (SET_SRC (PATTERN (n)), 0)) |
| && GET_CODE (XEXP (SET_SRC (PATTERN (n)), 1)) == CONST_INT) |
| { |
| int ic = INTVAL (XEXP (SET_SRC (PATTERN (i)), 1)); |
| int nc = INTVAL (XEXP (SET_SRC (PATTERN (n)), 1)); |
| if (REGNO (SET_DEST (PATTERN (i))) == REGNO (SET_DEST (PATTERN (n))) |
| && ic + nc < 32767 |
| && ic + nc > -32768) |
| { |
| XEXP (SET_SRC (PATTERN (i)), 1) = GEN_INT (ic + nc); |
| NEXT_INSN (i) = NEXT_INSN (n); |
| if (NEXT_INSN (i)) |
| PREV_INSN (NEXT_INSN (i)) = i; |
| } |
| } |
| } |
| } |
| |
| /* If this insn adjusts the stack, return the adjustment, else return |
| zero. */ |
| static int |
| add_sp_insn_p (rtx insn) |
| { |
| rtx pat; |
| |
| if (! single_set (insn)) |
| return 0; |
| pat = PATTERN (insn); |
| if (GET_CODE (SET_DEST (pat)) != REG) |
| return 0; |
| if (REGNO (SET_DEST (pat)) != SP_REGNO) |
| return 0; |
| if (GET_CODE (SET_SRC (pat)) != PLUS) |
| return 0; |
| if (GET_CODE (XEXP (SET_SRC (pat), 0)) != REG) |
| return 0; |
| if (REGNO (XEXP (SET_SRC (pat), 0)) != SP_REGNO) |
| return 0; |
| if (GET_CODE (XEXP (SET_SRC (pat), 1)) != CONST_INT) |
| return 0; |
| return INTVAL (XEXP (SET_SRC (pat), 1)); |
| } |
| |
| /* Check for trivial functions that set up an unneeded stack |
| frame. */ |
| static void |
| mep_reorg_noframe (rtx insns) |
| { |
| rtx start_frame_insn; |
| rtx end_frame_insn = 0; |
| int sp_adjust, sp2; |
| rtx sp; |
| |
| /* The first insn should be $sp = $sp + N */ |
| while (insns && ! INSN_P (insns)) |
| insns = NEXT_INSN (insns); |
| if (!insns) |
| return; |
| |
| sp_adjust = add_sp_insn_p (insns); |
| if (sp_adjust == 0) |
| return; |
| |
| start_frame_insn = insns; |
| sp = SET_DEST (PATTERN (start_frame_insn)); |
| |
| insns = next_real_insn (insns); |
| |
| while (insns) |
| { |
| rtx next = next_real_insn (insns); |
| if (!next) |
| break; |
| |
| sp2 = add_sp_insn_p (insns); |
| if (sp2) |
| { |
| if (end_frame_insn) |
| return; |
| end_frame_insn = insns; |
| if (sp2 != -sp_adjust) |
| return; |
| } |
| else if (mep_mentioned_p (insns, sp, 0)) |
| return; |
| else if (CALL_P (insns)) |
| return; |
| |
| insns = next; |
| } |
| |
| if (end_frame_insn) |
| { |
| delete_insn (start_frame_insn); |
| delete_insn (end_frame_insn); |
| } |
| } |
| |
| static void |
| mep_reorg (void) |
| { |
| rtx insns = get_insns (); |
| |
| /* We require accurate REG_DEAD notes. */ |
| compute_bb_for_insn (); |
| df_note_add_problem (); |
| df_analyze (); |
| |
| mep_reorg_addcombine (insns); |
| #if EXPERIMENTAL_REGMOVE_REORG |
| /* VLIW packing has been done already, so we can't just delete things. */ |
| if (!mep_vliw_function_p (cfun->decl)) |
| mep_reorg_regmove (insns); |
| #endif |
| mep_jmp_return_reorg (insns); |
| mep_bundle_insns (insns); |
| mep_reorg_repeat (insns); |
| if (optimize |
| && !profile_flag |
| && !profile_arc_flag |
| && TARGET_OPT_REPEAT |
| && (!mep_interrupt_p () || mep_interrupt_saved_reg (RPB_REGNO))) |
| mep_reorg_erepeat (insns); |
| |
| /* This may delete *insns so make sure it's last. */ |
| mep_reorg_noframe (insns); |
| |
| df_finish_pass (false); |
| } |
| |
| |
| |
| /*----------------------------------------------------------------------*/ |
| /* Builtins */ |
| /*----------------------------------------------------------------------*/ |
| |
| /* Element X gives the index into cgen_insns[] of the most general |
| implementation of intrinsic X. Unimplemented intrinsics are |
| mapped to -1. */ |
| int mep_intrinsic_insn[ARRAY_SIZE (cgen_intrinsics)]; |
| |
| /* Element X gives the index of another instruction that is mapped to |
| the same intrinsic as cgen_insns[X]. It is -1 when there is no other |
| instruction. |
| |
| Things are set up so that mep_intrinsic_chain[X] < X. */ |
| static int mep_intrinsic_chain[ARRAY_SIZE (cgen_insns)]; |
| |
| /* The bitmask for the current ISA. The ISA masks are declared |
| in mep-intrin.h. */ |
| unsigned int mep_selected_isa; |
| |
| struct mep_config { |
| const char *config_name; |
| unsigned int isa; |
| }; |
| |
| static struct mep_config mep_configs[] = { |
| #ifdef COPROC_SELECTION_TABLE |
| COPROC_SELECTION_TABLE, |
| #endif |
| { 0, 0 } |
| }; |
| |
| /* Initialize the global intrinsics variables above. */ |
| |
| static void |
| mep_init_intrinsics (void) |
| { |
| size_t i; |
| |
| /* Set MEP_SELECTED_ISA to the ISA flag for this configuration. */ |
| mep_selected_isa = mep_configs[0].isa; |
| if (mep_config_string != 0) |
| for (i = 0; mep_configs[i].config_name; i++) |
| if (strcmp (mep_config_string, mep_configs[i].config_name) == 0) |
| { |
| mep_selected_isa = mep_configs[i].isa; |
| break; |
| } |
| |
| /* Assume all intrinsics are unavailable. */ |
| for (i = 0; i < ARRAY_SIZE (mep_intrinsic_insn); i++) |
| mep_intrinsic_insn[i] = -1; |
| |
| /* Build up the global intrinsic tables. */ |
| for (i = 0; i < ARRAY_SIZE (cgen_insns); i++) |
| if ((cgen_insns[i].isas & mep_selected_isa) != 0) |
| { |
| mep_intrinsic_chain[i] = mep_intrinsic_insn[cgen_insns[i].intrinsic]; |
| mep_intrinsic_insn[cgen_insns[i].intrinsic] = i; |
| } |
| /* See whether we can directly move values between one coprocessor |
| register and another. */ |
| for (i = 0; i < ARRAY_SIZE (mep_cmov_insns); i++) |
| if (MEP_INTRINSIC_AVAILABLE_P (mep_cmov_insns[i])) |
| mep_have_copro_copro_moves_p = true; |
| |
| /* See whether we can directly move values between core and |
| coprocessor registers. */ |
| mep_have_core_copro_moves_p = (MEP_INTRINSIC_AVAILABLE_P (mep_cmov1) |
| && MEP_INTRINSIC_AVAILABLE_P (mep_cmov2)); |
| |
| mep_have_core_copro_moves_p = 1; |
| } |
| |
| /* Declare all available intrinsic functions. Called once only. */ |
| |
| static tree cp_data_bus_int_type_node; |
| static tree opaque_vector_type_node; |
| static tree v8qi_type_node; |
| static tree v4hi_type_node; |
| static tree v2si_type_node; |
| static tree v8uqi_type_node; |
| static tree v4uhi_type_node; |
| static tree v2usi_type_node; |
| |
| static tree |
| mep_cgen_regnum_to_type (enum cgen_regnum_operand_type cr) |
| { |
| switch (cr) |
| { |
| case cgen_regnum_operand_type_POINTER: return ptr_type_node; |
| case cgen_regnum_operand_type_LONG: return long_integer_type_node; |
| case cgen_regnum_operand_type_ULONG: return long_unsigned_type_node; |
| case cgen_regnum_operand_type_SHORT: return short_integer_type_node; |
| case cgen_regnum_operand_type_USHORT: return short_unsigned_type_node; |
| case cgen_regnum_operand_type_CHAR: return char_type_node; |
| case cgen_regnum_operand_type_UCHAR: return unsigned_char_type_node; |
| case cgen_regnum_operand_type_SI: return intSI_type_node; |
| case cgen_regnum_operand_type_DI: return intDI_type_node; |
| case cgen_regnum_operand_type_VECTOR: return opaque_vector_type_node; |
| case cgen_regnum_operand_type_V8QI: return v8qi_type_node; |
| case cgen_regnum_operand_type_V4HI: return v4hi_type_node; |
| case cgen_regnum_operand_type_V2SI: return v2si_type_node; |
| case cgen_regnum_operand_type_V8UQI: return v8uqi_type_node; |
| case cgen_regnum_operand_type_V4UHI: return v4uhi_type_node; |
| case cgen_regnum_operand_type_V2USI: return v2usi_type_node; |
| case cgen_regnum_operand_type_CP_DATA_BUS_INT: return cp_data_bus_int_type_node; |
| default: |
| return void_type_node; |
| } |
| } |
| |
| static void |
| mep_init_builtins (void) |
| { |
| size_t i; |
| |
| if (TARGET_64BIT_CR_REGS) |
| cp_data_bus_int_type_node = long_long_integer_type_node; |
| else |
| cp_data_bus_int_type_node = long_integer_type_node; |
| |
| opaque_vector_type_node = build_opaque_vector_type (intQI_type_node, 8); |
| v8qi_type_node = build_vector_type (intQI_type_node, 8); |
| v4hi_type_node = build_vector_type (intHI_type_node, 4); |
| v2si_type_node = build_vector_type (intSI_type_node, 2); |
| v8uqi_type_node = build_vector_type (unsigned_intQI_type_node, 8); |
| v4uhi_type_node = build_vector_type (unsigned_intHI_type_node, 4); |
| v2usi_type_node = build_vector_type (unsigned_intSI_type_node, 2); |
| |
| add_builtin_type ("cp_data_bus_int", cp_data_bus_int_type_node); |
| |
| add_builtin_type ("cp_vector", opaque_vector_type_node); |
| |
| add_builtin_type ("cp_v8qi", v8qi_type_node); |
| add_builtin_type ("cp_v4hi", v4hi_type_node); |
| add_builtin_type ("cp_v2si", v2si_type_node); |
| |
| add_builtin_type ("cp_v8uqi", v8uqi_type_node); |
| add_builtin_type ("cp_v4uhi", v4uhi_type_node); |
| add_builtin_type ("cp_v2usi", v2usi_type_node); |
| |
| /* Intrinsics like mep_cadd3 are implemented with two groups of |
| instructions, one which uses UNSPECs and one which uses a specific |
| rtl code such as PLUS. Instructions in the latter group belong |
| to GROUP_KNOWN_CODE. |
| |
| In such cases, the intrinsic will have two entries in the global |
| tables above. The unspec form is accessed using builtin functions |
| while the specific form is accessed using the mep_* enum in |
| mep-intrin.h. |
| |
| The idea is that __cop arithmetic and builtin functions have |
| different optimization requirements. If mep_cadd3() appears in |
| the source code, the user will surely except gcc to use cadd3 |
| rather than a work-alike such as add3. However, if the user |
| just writes "a + b", where a or b are __cop variables, it is |
| reasonable for gcc to choose a core instruction rather than |
| cadd3 if it believes that is more optimal. */ |
| for (i = 0; i < ARRAY_SIZE (cgen_insns); i++) |
| if ((cgen_insns[i].groups & GROUP_KNOWN_CODE) == 0 |
| && mep_intrinsic_insn[cgen_insns[i].intrinsic] >= 0) |
| { |
| tree ret_type = void_type_node; |
| tree bi_type; |
| |
| if (i > 0 && cgen_insns[i].intrinsic == cgen_insns[i-1].intrinsic) |
| continue; |
| |
| if (cgen_insns[i].cret_p) |
| ret_type = mep_cgen_regnum_to_type (cgen_insns[i].regnums[0].type); |
| |
| bi_type = build_function_type_list (ret_type, NULL_TREE); |
| add_builtin_function (cgen_intrinsics[cgen_insns[i].intrinsic], |
| bi_type, |
| cgen_insns[i].intrinsic, BUILT_IN_MD, NULL, NULL); |
| } |
| } |
| |
| /* Report the unavailablity of the given intrinsic. */ |
| |
| #if 1 |
| static void |
| mep_intrinsic_unavailable (int intrinsic) |
| { |
| static int already_reported_p[ARRAY_SIZE (cgen_intrinsics)]; |
| |
| if (already_reported_p[intrinsic]) |
| return; |
| |
| if (mep_intrinsic_insn[intrinsic] < 0) |
| error ("coprocessor intrinsic %qs is not available in this configuration", |
| cgen_intrinsics[intrinsic]); |
| else if (CGEN_CURRENT_GROUP == GROUP_VLIW) |
| error ("%qs is not available in VLIW functions", |
| cgen_intrinsics[intrinsic]); |
| else |
| error ("%qs is not available in non-VLIW functions", |
| cgen_intrinsics[intrinsic]); |
| |
| already_reported_p[intrinsic] = 1; |
| } |
| #endif |
| |
| |
| /* See if any implementation of INTRINSIC is available to the |
| current function. If so, store the most general implementation |
| in *INSN_PTR and return true. Return false otherwise. */ |
| |
| static bool |
| mep_get_intrinsic_insn (int intrinsic ATTRIBUTE_UNUSED, const struct cgen_insn **insn_ptr ATTRIBUTE_UNUSED) |
| { |
| int i; |
| |
| i = mep_intrinsic_insn[intrinsic]; |
| while (i >= 0 && !CGEN_ENABLE_INSN_P (i)) |
| i = mep_intrinsic_chain[i]; |
| |
| if (i >= 0) |
| { |
| *insn_ptr = &cgen_insns[i]; |
| return true; |
| } |
| return false; |
| } |
| |
| |
| /* Like mep_get_intrinsic_insn, but with extra handling for moves. |
| If INTRINSIC is mep_cmov, but there is no pure CR <- CR move insn, |
| try using a work-alike instead. In this case, the returned insn |
| may have three operands rather than two. */ |
| |
| static bool |
| mep_get_move_insn (int intrinsic, const struct cgen_insn **cgen_insn) |
| { |
| size_t i; |
| |
| if (intrinsic == mep_cmov) |
| { |
| for (i = 0; i < ARRAY_SIZE (mep_cmov_insns); i++) |
| if (mep_get_intrinsic_insn (mep_cmov_insns[i], cgen_insn)) |
| return true; |
| return false; |
| } |
| return mep_get_intrinsic_insn (intrinsic, cgen_insn); |
| } |
| |
| |
| /* If ARG is a register operand that is the same size as MODE, convert it |
| to MODE using a subreg. Otherwise return ARG as-is. */ |
| |
| static rtx |
| mep_convert_arg (enum machine_mode mode, rtx arg) |
| { |
| if (GET_MODE (arg) != mode |
| && register_operand (arg, VOIDmode) |
| && GET_MODE_SIZE (GET_MODE (arg)) == GET_MODE_SIZE (mode)) |
| return simplify_gen_subreg (mode, arg, GET_MODE (arg), 0); |
| return arg; |
| } |
| |
| |
| /* Apply regnum conversions to ARG using the description given by REGNUM. |
| Return the new argument on success and null on failure. */ |
| |
| static rtx |
| mep_convert_regnum (const struct cgen_regnum_operand *regnum, rtx arg) |
| { |
| if (regnum->count == 0) |
| return arg; |
| |
| if (GET_CODE (arg) != CONST_INT |
| || INTVAL (arg) < 0 |
| || INTVAL (arg) >= regnum->count) |
| return 0; |
| |
| return gen_rtx_REG (SImode, INTVAL (arg) + regnum->base); |
| } |
| |
| |
| /* Try to make intrinsic argument ARG match the given operand. |
| UNSIGNED_P is true if the argument has an unsigned type. */ |
| |
| static rtx |
| mep_legitimize_arg (const struct insn_operand_data *operand, rtx arg, |
| int unsigned_p) |
| { |
| if (GET_CODE (arg) == CONST_INT) |
| { |
| /* CONST_INTs can only be bound to integer operands. */ |
| if (GET_MODE_CLASS (operand->mode) != MODE_INT) |
| return 0; |
| } |
| else if (GET_CODE (arg) == CONST_DOUBLE) |
| /* These hold vector constants. */; |
| else if (GET_MODE_SIZE (GET_MODE (arg)) != GET_MODE_SIZE (operand->mode)) |
| { |
| /* If the argument is a different size from what's expected, we must |
| have a value in the right mode class in order to convert it. */ |
| if (GET_MODE_CLASS (operand->mode) != GET_MODE_CLASS (GET_MODE (arg))) |
| return 0; |
| |
| /* If the operand is an rvalue, promote or demote it to match the |
| operand's size. This might not need extra instructions when |
| ARG is a register value. */ |
| if (operand->constraint[0] != '=') |
| arg = convert_to_mode (operand->mode, arg, unsigned_p); |
| } |
| |
| /* If the operand is an lvalue, bind the operand to a new register. |
| The caller will copy this value into ARG after the main |
| instruction. By doing this always, we produce slightly more |
| optimal code. */ |
| /* But not for control registers. */ |
| if (operand->constraint[0] == '=' |
| && (! REG_P (arg) |
| || ! (CONTROL_REGNO_P (REGNO (arg)) |
| || CCR_REGNO_P (REGNO (arg)) |
| || CR_REGNO_P (REGNO (arg))) |
| )) |
| return gen_reg_rtx (operand->mode); |
| |
| /* Try simple mode punning. */ |
| arg = mep_convert_arg (operand->mode, arg); |
| if (operand->predicate (arg, operand->mode)) |
| return arg; |
| |
| /* See if forcing the argument into a register will make it match. */ |
| if (GET_CODE (arg) == CONST_INT || GET_CODE (arg) == CONST_DOUBLE) |
| arg = force_reg (operand->mode, arg); |
| else |
| arg = mep_convert_arg (operand->mode, force_reg (GET_MODE (arg), arg)); |
| if (operand->predicate (arg, operand->mode)) |
| return arg; |
| |
| return 0; |
| } |
| |
| |
| /* Report that ARG cannot be passed to argument ARGNUM of intrinsic |
| function FNNAME. OPERAND describes the operand to which ARGNUM |
| is mapped. */ |
| |
| static void |
| mep_incompatible_arg (const struct insn_operand_data *operand, rtx arg, |
| int argnum, tree fnname) |
| { |
| size_t i; |
| |
| if (GET_CODE (arg) == CONST_INT) |
| for (i = 0; i < ARRAY_SIZE (cgen_immediate_predicates); i++) |
| if (operand->predicate == cgen_immediate_predicates[i].predicate) |
| { |
| const struct cgen_immediate_predicate *predicate; |
| HOST_WIDE_INT argval; |
| |
| predicate = &cgen_immediate_predicates[i]; |
| argval = INTVAL (arg); |
| if (argval < predicate->lower || argval >= predicate->upper) |
| error ("argument %d of %qE must be in the range %d...%d", |
| argnum, fnname, predicate->lower, predicate->upper - 1); |
| else |
| error ("argument %d of %qE must be a multiple of %d", |
| argnum, fnname, predicate->align); |
| return; |
| } |
| |
| error ("incompatible type for argument %d of %qE", argnum, fnname); |
| } |
| |
| static rtx |
| mep_expand_builtin (tree exp, rtx target ATTRIBUTE_UNUSED, |
| rtx subtarget ATTRIBUTE_UNUSED, |
| enum machine_mode mode ATTRIBUTE_UNUSED, |
| int ignore ATTRIBUTE_UNUSED) |
| { |
| rtx pat, op[10], arg[10]; |
| unsigned int a; |
| int opindex, unsigned_p[10]; |
| tree fndecl, args; |
| unsigned int n_args; |
| tree fnname; |
| const struct cgen_insn *cgen_insn; |
| const struct insn_data_d *idata; |
| unsigned int first_arg = 0; |
| unsigned int builtin_n_args; |
| |
| fndecl = TREE_OPERAND (CALL_EXPR_FN (exp), 0); |
| fnname = DECL_NAME (fndecl); |
| |
| /* Find out which instruction we should emit. Note that some coprocessor |
| intrinsics may only be available in VLIW mode, or only in normal mode. */ |
| if (!mep_get_intrinsic_insn (DECL_FUNCTION_CODE (fndecl), &cgen_insn)) |
| { |
| mep_intrinsic_unavailable (DECL_FUNCTION_CODE (fndecl)); |
| return NULL_RTX; |
| } |
| idata = &insn_data[cgen_insn->icode]; |
| |
| builtin_n_args = cgen_insn->num_args; |
| |
| if (cgen_insn->cret_p) |
| { |
| if (cgen_insn->cret_p > 1) |
| builtin_n_args ++; |
| first_arg = 1; |
| mep_cgen_regnum_to_type (cgen_insn->regnums[0].type); |
| builtin_n_args --; |
| } |
| |
| /* Evaluate each argument. */ |
| n_args = call_expr_nargs (exp); |
| |
| if (n_args < builtin_n_args) |
| { |
| error ("too few arguments to %qE", fnname); |
| return NULL_RTX; |
| } |
| if (n_args > builtin_n_args) |
| { |
| error ("too many arguments to %qE", fnname); |
| return NULL_RTX; |
| } |
| |
| for (a = first_arg; a < builtin_n_args + first_arg; a++) |
| { |
| tree value; |
| |
| args = CALL_EXPR_ARG (exp, a - first_arg); |
| |
| value = args; |
| |
| #if 0 |
| if (cgen_insn->regnums[a].reference_p) |
| { |
| if (TREE_CODE (value) != ADDR_EXPR) |
| { |
| debug_tree(value); |
| error ("argument %d of %qE must be an address", a+1, fnname); |
| return NULL_RTX; |
| } |
| value = TREE_OPERAND (value, 0); |
| } |
| #endif |
| |
| /* If the argument has been promoted to int, get the unpromoted |
| value. This is necessary when sub-int memory values are bound |
| to reference parameters. */ |
| if (TREE_CODE (value) == NOP_EXPR |
| && TREE_TYPE (value) == integer_type_node |
| && INTEGRAL_TYPE_P (TREE_TYPE (TREE_OPERAND (value, 0))) |
| && (TYPE_PRECISION (TREE_TYPE (TREE_OPERAND (value, 0))) |
| < TYPE_PRECISION (TREE_TYPE (value)))) |
| value = TREE_OPERAND (value, 0); |
| |
| /* If the argument has been promoted to double, get the unpromoted |
| SFmode value. This is necessary for FMAX support, for example. */ |
| if (TREE_CODE (value) == NOP_EXPR |
| && SCALAR_FLOAT_TYPE_P (TREE_TYPE (value)) |
| && SCALAR_FLOAT_TYPE_P (TREE_TYPE (TREE_OPERAND (value, 0))) |
| && TYPE_MODE (TREE_TYPE (value)) == DFmode |
| && TYPE_MODE (TREE_TYPE (TREE_OPERAND (value, 0))) == SFmode) |
| value = TREE_OPERAND (value, 0); |
| |
| unsigned_p[a] = TYPE_UNSIGNED (TREE_TYPE (value)); |
| arg[a] = expand_expr (value, NULL, VOIDmode, EXPAND_NORMAL); |
| arg[a] = mep_convert_regnum (&cgen_insn->regnums[a], arg[a]); |
| if (cgen_insn->regnums[a].reference_p) |
| { |
| tree pointed_to = TREE_TYPE (TREE_TYPE (value)); |
| enum machine_mode pointed_mode = TYPE_MODE (pointed_to); |
| |
| arg[a] = gen_rtx_MEM (pointed_mode, arg[a]); |
| } |
| if (arg[a] == 0) |
| { |
| error ("argument %d of %qE must be in the range %d...%d", |
| a + 1, fnname, 0, cgen_insn->regnums[a].count - 1); |
| return NULL_RTX; |
| } |
| } |
| |
| for (a = 0; a < first_arg; a++) |
| { |
| if (a == 0 && target && GET_MODE (target) == idata->operand[0].mode) |
| arg[a] = target; |
| else |
| arg[a] = gen_reg_rtx (idata->operand[0].mode); |
| } |
| |
| /* Convert the arguments into a form suitable for the intrinsic. |
| Report an error if this isn't possible. */ |
| for (opindex = 0; opindex < idata->n_operands; opindex++) |
| { |
| a = cgen_insn->op_mapping[opindex]; |
| op[opindex] = mep_legitimize_arg (&idata->operand[opindex], |
| arg[a], unsigned_p[a]); |
| if (op[opindex] == 0) |
| { |
| mep_incompatible_arg (&idata->operand[opindex], |
| arg[a], a + 1 - first_arg, fnname); |
| return NULL_RTX; |
| } |
| } |
| |
| /* Emit the instruction. */ |
| pat = idata->genfun (op[0], op[1], op[2], op[3], op[4], |
| op[5], op[6], op[7], op[8], op[9]); |
| |
| if (GET_CODE (pat) == SET |
| && GET_CODE (SET_DEST (pat)) == PC |
| && GET_CODE (SET_SRC (pat)) == IF_THEN_ELSE) |
| emit_jump_insn (pat); |
| else |
| emit_insn (pat); |
| |
| /* Copy lvalues back to their final locations. */ |
| for (opindex = 0; opindex < idata->n_operands; opindex++) |
| if (idata->operand[opindex].constraint[0] == '=') |
| { |
| a = cgen_insn->op_mapping[opindex]; |
| if (a >= first_arg) |
| { |
| if (GET_MODE_CLASS (GET_MODE (arg[a])) |
| != GET_MODE_CLASS (GET_MODE (op[opindex]))) |
| emit_move_insn (arg[a], gen_lowpart (GET_MODE (arg[a]), |
| op[opindex])); |
| else |
| { |
| /* First convert the operand to the right mode, then copy it |
| into the destination. Doing the conversion as a separate |
| step (rather than using convert_move) means that we can |
| avoid creating no-op moves when ARG[A] and OP[OPINDEX] |
| refer to the same register. */ |
| op[opindex] = convert_to_mode (GET_MODE (arg[a]), |
| op[opindex], unsigned_p[a]); |
| if (!rtx_equal_p (arg[a], op[opindex])) |
| emit_move_insn (arg[a], op[opindex]); |
| } |
| } |
| } |
| |
| if (first_arg > 0 && target && target != op[0]) |
| { |
| emit_move_insn (target, op[0]); |
| } |
| |
| return target; |
| } |
| |
| static bool |
| mep_vector_mode_supported_p (enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return false; |
| } |
| |
| /* A subroutine of global_reg_mentioned_p, returns 1 if *LOC mentions |
| a global register. */ |
| |
| static int |
| global_reg_mentioned_p_1 (rtx *loc, void *data ATTRIBUTE_UNUSED) |
| { |
| int regno; |
| rtx x = *loc; |
| |
| if (! x) |
| return 0; |
| |
| switch (GET_CODE (x)) |
| { |
| case SUBREG: |
| if (REG_P (SUBREG_REG (x))) |
| { |
| if (REGNO (SUBREG_REG (x)) < FIRST_PSEUDO_REGISTER |
| && global_regs[subreg_regno (x)]) |
| return 1; |
| return 0; |
| } |
| break; |
| |
| case REG: |
| regno = REGNO (x); |
| if (regno < FIRST_PSEUDO_REGISTER && global_regs[regno]) |
| return 1; |
| return 0; |
| |
| case SCRATCH: |
| case PC: |
| case CC0: |
| case CONST_INT: |
| case CONST_DOUBLE: |
| case CONST: |
| case LABEL_REF: |
| return 0; |
| |
| case CALL: |
| /* A non-constant call might use a global register. */ |
| return 1; |
| |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| /* Returns nonzero if X mentions a global register. */ |
| |
| static int |
| global_reg_mentioned_p (rtx x) |
| { |
| if (INSN_P (x)) |
| { |
| if (CALL_P (x)) |
| { |
| if (! RTL_CONST_OR_PURE_CALL_P (x)) |
| return 1; |
| x = CALL_INSN_FUNCTION_USAGE (x); |
| if (x == 0) |
| return 0; |
| } |
| else |
| x = PATTERN (x); |
| } |
| |
| return for_each_rtx (&x, global_reg_mentioned_p_1, NULL); |
| } |
| /* Scheduling hooks for VLIW mode. |
| |
| Conceptually this is very simple: we have a two-pack architecture |
| that takes one core insn and one coprocessor insn to make up either |
| a 32- or 64-bit instruction word (depending on the option bit set in |
| the chip). I.e. in VL32 mode, we can pack one 16-bit core insn and |
| one 16-bit cop insn; in VL64 mode we can pack one 16-bit core insn |
| and one 48-bit cop insn or two 32-bit core/cop insns. |
| |
| In practice, instruction selection will be a bear. Consider in |
| VL64 mode the following insns |
| |
| add $1, 1 |
| cmov $cr0, $0 |
| |
| these cannot pack, since the add is a 16-bit core insn and cmov |
| is a 32-bit cop insn. However, |
| |
| add3 $1, $1, 1 |
| cmov $cr0, $0 |
| |
| packs just fine. For good VLIW code generation in VL64 mode, we |
| will have to have 32-bit alternatives for many of the common core |
| insns. Not implemented. */ |
| |
| static int |
| mep_adjust_cost (rtx insn, rtx link, rtx dep_insn, int cost) |
| { |
| int cost_specified; |
| |
| if (REG_NOTE_KIND (link) != 0) |
| { |
| /* See whether INSN and DEP_INSN are intrinsics that set the same |
| hard register. If so, it is more important to free up DEP_INSN |
| than it is to free up INSN. |
| |
| Note that intrinsics like mep_mulr are handled differently from |
| the equivalent mep.md patterns. In mep.md, if we don't care |
| about the value of $lo and $hi, the pattern will just clobber |
| the registers, not set them. Since clobbers don't count as |
| output dependencies, it is often possible to reorder two mulrs, |
| even after reload. |
| |
| In contrast, mep_mulr() sets both $lo and $hi to specific values, |
| so any pair of mep_mulr()s will be inter-dependent. We should |
| therefore give the first mep_mulr() a higher priority. */ |
| if (REG_NOTE_KIND (link) == REG_DEP_OUTPUT |
| && global_reg_mentioned_p (PATTERN (insn)) |
| && global_reg_mentioned_p (PATTERN (dep_insn))) |
| return 1; |
| |
| /* If the dependence is an anti or output dependence, assume it |
| has no cost. */ |
| return 0; |
| } |
| |
| /* If we can't recognize the insns, we can't really do anything. */ |
| if (recog_memoized (dep_insn) < 0) |
| return cost; |
| |
| /* The latency attribute doesn't apply to MeP-h1: we use the stall |
| attribute instead. */ |
| if (!TARGET_H1) |
| { |
| cost_specified = get_attr_latency (dep_insn); |
| if (cost_specified != 0) |
| return cost_specified; |
| } |
| |
| return cost; |
| } |
| |
| /* ??? We don't properly compute the length of a load/store insn, |
| taking into account the addressing mode. */ |
| |
| static int |
| mep_issue_rate (void) |
| { |
| return TARGET_IVC2 ? 3 : 2; |
| } |
| |
| /* Return true if function DECL was declared with the vliw attribute. */ |
| |
| bool |
| mep_vliw_function_p (tree decl) |
| { |
| return lookup_attribute ("vliw", TYPE_ATTRIBUTES (TREE_TYPE (decl))) != 0; |
| } |
| |
| static rtx |
| mep_find_ready_insn (rtx *ready, int nready, enum attr_slot slot, int length) |
| { |
| int i; |
| |
| for (i = nready - 1; i >= 0; --i) |
| { |
| rtx insn = ready[i]; |
| if (recog_memoized (insn) >= 0 |
| && get_attr_slot (insn) == slot |
| && get_attr_length (insn) == length) |
| return insn; |
| } |
| |
| return NULL_RTX; |
| } |
| |
| static void |
| mep_move_ready_insn (rtx *ready, int nready, rtx insn) |
| { |
| int i; |
| |
| for (i = 0; i < nready; ++i) |
| if (ready[i] == insn) |
| { |
| for (; i < nready - 1; ++i) |
| ready[i] = ready[i + 1]; |
| ready[i] = insn; |
| return; |
| } |
| |
| gcc_unreachable (); |
| } |
| |
| static void |
| mep_print_sched_insn (FILE *dump, rtx insn) |
| { |
| const char *slots = "none"; |
| const char *name = NULL; |
| int code; |
| char buf[30]; |
| |
| if (GET_CODE (PATTERN (insn)) == SET |
| || GET_CODE (PATTERN (insn)) == PARALLEL) |
| { |
| switch (get_attr_slots (insn)) |
| { |
| case SLOTS_CORE: slots = "core"; break; |
| case SLOTS_C3: slots = "c3"; break; |
| case SLOTS_P0: slots = "p0"; break; |
| case SLOTS_P0_P0S: slots = "p0,p0s"; break; |
| case SLOTS_P0_P1: slots = "p0,p1"; break; |
| case SLOTS_P0S: slots = "p0s"; break; |
| case SLOTS_P0S_P1: slots = "p0s,p1"; break; |
| case SLOTS_P1: slots = "p1"; break; |
| default: |
| sprintf(buf, "%d", get_attr_slots (insn)); |
| slots = buf; |
| break; |
| } |
| } |
| if (GET_CODE (PATTERN (insn)) == USE) |
| slots = "use"; |
| |
| code = INSN_CODE (insn); |
| if (code >= 0) |
| name = get_insn_name (code); |
| if (!name) |
| name = "{unknown}"; |
| |
| fprintf (dump, |
| "insn %4d %4d %8s %s\n", |
| code, |
| INSN_UID (insn), |
| name, |
| slots); |
| } |
| |
| static int |
| mep_sched_reorder (FILE *dump ATTRIBUTE_UNUSED, |
| int sched_verbose ATTRIBUTE_UNUSED, rtx *ready, |
| int *pnready, int clock ATTRIBUTE_UNUSED) |
| { |
| int nready = *pnready; |
| rtx core_insn, cop_insn; |
| int i; |
| |
| if (dump && sched_verbose > 1) |
| { |
| fprintf (dump, "\nsched_reorder: clock %d nready %d\n", clock, nready); |
| for (i=0; i<nready; i++) |
| mep_print_sched_insn (dump, ready[i]); |
| fprintf (dump, "\n"); |
| } |
| |
| if (!mep_vliw_function_p (cfun->decl)) |
| return 1; |
| if (nready < 2) |
| return 1; |
| |
| /* IVC2 uses a DFA to determine what's ready and what's not. */ |
| if (TARGET_IVC2) |
| return nready; |
| |
| /* We can issue either a core or coprocessor instruction. |
| Look for a matched pair of insns to reorder. If we don't |
| find any, don't second-guess the scheduler's priorities. */ |
| |
| if ((core_insn = mep_find_ready_insn (ready, nready, SLOT_CORE, 2)) |
| && (cop_insn = mep_find_ready_insn (ready, nready, SLOT_COP, |
| TARGET_OPT_VL64 ? 6 : 2))) |
| ; |
| else if (TARGET_OPT_VL64 |
| && (core_insn = mep_find_ready_insn (ready, nready, SLOT_CORE, 4)) |
| && (cop_insn = mep_find_ready_insn (ready, nready, SLOT_COP, 4))) |
| ; |
| else |
| /* We didn't find a pair. Issue the single insn at the head |
| of the ready list. */ |
| return 1; |
| |
| /* Reorder the two insns first. */ |
| mep_move_ready_insn (ready, nready, core_insn); |
| mep_move_ready_insn (ready, nready - 1, cop_insn); |
| return 2; |
| } |
| |
| /* A for_each_rtx callback. Return true if *X is a register that is |
| set by insn PREV. */ |
| |
| static int |
| mep_store_find_set (rtx *x, void *prev) |
| { |
| return REG_P (*x) && reg_set_p (*x, (const_rtx) prev); |
| } |
| |
| /* Like mep_store_bypass_p, but takes a pattern as the second argument, |
| not the containing insn. */ |
| |
| static bool |
| mep_store_data_bypass_1 (rtx prev, rtx pat) |
| { |
| /* Cope with intrinsics like swcpa. */ |
| if (GET_CODE (pat) == PARALLEL) |
| { |
| int i; |
| |
| for (i = 0; i < XVECLEN (pat, 0); i++) |
| if (mep_store_data_bypass_p (prev, XVECEXP (pat, 0, i))) |
| return true; |
| |
| return false; |
| } |
| |
| /* Check for some sort of store. */ |
| if (GET_CODE (pat) != SET |
| || GET_CODE (SET_DEST (pat)) != MEM) |
| return false; |
| |
| /* Intrinsics use patterns of the form (set (mem (scratch)) (unspec ...)). |
| The first operand to the unspec is the store data and the other operands |
| are used to calculate the address. */ |
| if (GET_CODE (SET_SRC (pat)) == UNSPEC) |
| { |
| rtx src; |
| int i; |
| |
| src = SET_SRC (pat); |
| for (i = 1; i < XVECLEN (src, 0); i++) |
| if (for_each_rtx (&XVECEXP (src, 0, i), mep_store_find_set, prev)) |
| return false; |
| |
| return true; |
| } |
| |
| /* Otherwise just check that PREV doesn't modify any register mentioned |
| in the memory destination. */ |
| return !for_each_rtx (&SET_DEST (pat), mep_store_find_set, prev); |
| } |
| |
| /* Return true if INSN is a store instruction and if the store address |
| has no true dependence on PREV. */ |
| |
| bool |
| mep_store_data_bypass_p (rtx prev, rtx insn) |
| { |
| return INSN_P (insn) ? mep_store_data_bypass_1 (prev, PATTERN (insn)) : false; |
| } |
| |
| /* A for_each_rtx subroutine of mep_mul_hilo_bypass_p. Return 1 if *X |
| is a register other than LO or HI and if PREV sets *X. */ |
| |
| static int |
| mep_mul_hilo_bypass_1 (rtx *x, void *prev) |
| { |
| return (REG_P (*x) |
| && REGNO (*x) != LO_REGNO |
| && REGNO (*x) != HI_REGNO |
| && reg_set_p (*x, (const_rtx) prev)); |
| } |
| |
| /* Return true if, apart from HI/LO, there are no true dependencies |
| between multiplication instructions PREV and INSN. */ |
| |
| bool |
| mep_mul_hilo_bypass_p (rtx prev, rtx insn) |
| { |
| rtx pat; |
| |
| pat = PATTERN (insn); |
| if (GET_CODE (pat) == PARALLEL) |
| pat = XVECEXP (pat, 0, 0); |
| return (GET_CODE (pat) == SET |
| && !for_each_rtx (&SET_SRC (pat), mep_mul_hilo_bypass_1, prev)); |
| } |
| |
| /* Return true if INSN is an ldc instruction that issues to the |
| MeP-h1 integer pipeline. This is true for instructions that |
| read from PSW, LP, SAR, HI and LO. */ |
| |
| bool |
| mep_ipipe_ldc_p (rtx insn) |
| { |
| rtx pat, src; |
| |
| pat = PATTERN (insn); |
| |
| /* Cope with instrinsics that set both a hard register and its shadow. |
| The set of the hard register comes first. */ |
| if (GET_CODE (pat) == PARALLEL) |
| pat = XVECEXP (pat, 0, 0); |
| |
| if (GET_CODE (pat) == SET) |
| { |
| src = SET_SRC (pat); |
| |
| /* Cope with intrinsics. The first operand to the unspec is |
| the source register. */ |
| if (GET_CODE (src) == UNSPEC || GET_CODE (src) == UNSPEC_VOLATILE) |
| src = XVECEXP (src, 0, 0); |
| |
| if (REG_P (src)) |
| switch (REGNO (src)) |
| { |
| case PSW_REGNO: |
| case LP_REGNO: |
| case SAR_REGNO: |
| case HI_REGNO: |
| case LO_REGNO: |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* Create a VLIW bundle from core instruction CORE and coprocessor |
| instruction COP. COP always satisfies INSN_P, but CORE can be |
| either a new pattern or an existing instruction. |
| |
| Emit the bundle in place of COP and return it. */ |
| |
| static rtx |
| mep_make_bundle (rtx core, rtx cop) |
| { |
| rtx insn; |
| |
| /* If CORE is an existing instruction, remove it, otherwise put |
| the new pattern in an INSN harness. */ |
| if (INSN_P (core)) |
| remove_insn (core); |
| else |
| core = make_insn_raw (core); |
| |
| /* Generate the bundle sequence and replace COP with it. */ |
| insn = gen_rtx_SEQUENCE (VOIDmode, gen_rtvec (2, core, cop)); |
| insn = emit_insn_after (insn, cop); |
| remove_insn (cop); |
| |
| /* Set up the links of the insns inside the SEQUENCE. */ |
| PREV_INSN (core) = PREV_INSN (insn); |
| NEXT_INSN (core) = cop; |
| PREV_INSN (cop) = core; |
| NEXT_INSN (cop) = NEXT_INSN (insn); |
| |
| /* Set the VLIW flag for the coprocessor instruction. */ |
| PUT_MODE (core, VOIDmode); |
| PUT_MODE (cop, BImode); |
| |
| /* Derive a location for the bundle. Individual instructions cannot |
| have their own location because there can be no assembler labels |
| between CORE and COP. */ |
| INSN_LOCATION (insn) = INSN_LOCATION (INSN_LOCATION (core) ? core : cop); |
| INSN_LOCATION (core) = 0; |
| INSN_LOCATION (cop) = 0; |
| |
| return insn; |
| } |
| |
| /* A helper routine for ms1_insn_dependent_p called through note_stores. */ |
| |
| static void |
| mep_insn_dependent_p_1 (rtx x, const_rtx pat ATTRIBUTE_UNUSED, void *data) |
| { |
| rtx * pinsn = (rtx *) data; |
| |
| if (*pinsn && reg_mentioned_p (x, *pinsn)) |
| *pinsn = NULL_RTX; |
| } |
| |
| /* Return true if anything in insn X is (anti,output,true) dependent on |
| anything in insn Y. */ |
| |
| static int |
| mep_insn_dependent_p (rtx x, rtx y) |
| { |
| rtx tmp; |
| |
| gcc_assert (INSN_P (x)); |
| gcc_assert (INSN_P (y)); |
| |
| tmp = PATTERN (y); |
| note_stores (PATTERN (x), mep_insn_dependent_p_1, &tmp); |
| if (tmp == NULL_RTX) |
| return 1; |
| |
| tmp = PATTERN (x); |
| note_stores (PATTERN (y), mep_insn_dependent_p_1, &tmp); |
| if (tmp == NULL_RTX) |
| return 1; |
| |
| return 0; |
| } |
| |
| static int |
| core_insn_p (rtx insn) |
| { |
| if (GET_CODE (PATTERN (insn)) == USE) |
| return 0; |
| if (get_attr_slot (insn) == SLOT_CORE) |
| return 1; |
| return 0; |
| } |
| |
| /* Mark coprocessor instructions that can be bundled together with |
| the immediately preceding core instruction. This is later used |
| to emit the "+" that tells the assembler to create a VLIW insn. |
| |
| For unbundled insns, the assembler will automatically add coprocessor |
| nops, and 16-bit core nops. Due to an apparent oversight in the |
| spec, the assembler will _not_ automatically add 32-bit core nops, |
| so we have to emit those here. |
| |
| Called from mep_insn_reorg. */ |
| |
| static void |
| mep_bundle_insns (rtx insns) |
| { |
| rtx insn, last = NULL_RTX, first = NULL_RTX; |
| int saw_scheduling = 0; |
| |
| /* Only do bundling if we're in vliw mode. */ |
| if (!mep_vliw_function_p (cfun->decl)) |
| return; |
| |
| /* The first insn in a bundle are TImode, the remainder are |
| VOIDmode. After this function, the first has VOIDmode and the |
| rest have BImode. */ |
| |
| /* Note: this doesn't appear to be true for JUMP_INSNs. */ |
| |
| /* First, move any NOTEs that are within a bundle, to the beginning |
| of the bundle. */ |
| for (insn = insns; insn ; insn = NEXT_INSN (insn)) |
| { |
| if (NOTE_P (insn) && first) |
| /* Don't clear FIRST. */; |
| |
| else if (NONJUMP_INSN_P (insn) && GET_MODE (insn) == TImode) |
| first = insn; |
| |
| else if (NONJUMP_INSN_P (insn) && GET_MODE (insn) == VOIDmode && first) |
| { |
| rtx note, prev; |
| |
| /* INSN is part of a bundle; FIRST is the first insn in that |
| bundle. Move all intervening notes out of the bundle. |
| In addition, since the debug pass may insert a label |
| whenever the current line changes, set the location info |
| for INSN to match FIRST. */ |
| |
| INSN_LOCATION (insn) = INSN_LOCATION (first); |
| |
| note = PREV_INSN (insn); |
| while (note && note != first) |
| { |
| prev = PREV_INSN (note); |
| |
| if (NOTE_P (note)) |
| { |
| /* Remove NOTE from here... */ |
| PREV_INSN (NEXT_INSN (note)) = PREV_INSN (note); |
| NEXT_INSN (PREV_INSN (note)) = NEXT_INSN (note); |
| /* ...and put it in here. */ |
| NEXT_INSN (note) = first; |
| PREV_INSN (note) = PREV_INSN (first); |
| NEXT_INSN (PREV_INSN (note)) = note; |
| PREV_INSN (NEXT_INSN (note)) = note; |
| } |
| |
| note = prev; |
| } |
| } |
| |
| else if (!NONJUMP_INSN_P (insn)) |
| first = 0; |
| } |
| |
| /* Now fix up the bundles. */ |
| for (insn = insns; insn ; insn = NEXT_INSN (insn)) |
| { |
| if (NOTE_P (insn)) |
| continue; |
| |
| if (!NONJUMP_INSN_P (insn)) |
| { |
| last = 0; |
| continue; |
| } |
| |
| /* If we're not optimizing enough, there won't be scheduling |
| info. We detect that here. */ |
| if (GET_MODE (insn) == TImode) |
| saw_scheduling = 1; |
| if (!saw_scheduling) |
| continue; |
| |
| if (TARGET_IVC2) |
| { |
| rtx core_insn = NULL_RTX; |
| |
| /* IVC2 slots are scheduled by DFA, so we just accept |
| whatever the scheduler gives us. However, we must make |
| sure the core insn (if any) is the first in the bundle. |
| The IVC2 assembler can insert whatever NOPs are needed, |
| and allows a COP insn to be first. */ |
| |
| if (NONJUMP_INSN_P (insn) |
| && GET_CODE (PATTERN (insn)) != USE |
| && GET_MODE (insn) == TImode) |
| { |
| for (last = insn; |
| NEXT_INSN (last) |
| && GET_MODE (NEXT_INSN (last)) == VOIDmode |
| && NONJUMP_INSN_P (NEXT_INSN (last)); |
| last = NEXT_INSN (last)) |
| { |
| if (core_insn_p (last)) |
| core_insn = last; |
| } |
| if (core_insn_p (last)) |
| core_insn = last; |
| |
| if (core_insn && core_insn != insn) |
| { |
| /* Swap core insn to first in the bundle. */ |
| |
| /* Remove core insn. */ |
| if (PREV_INSN (core_insn)) |
| NEXT_INSN (PREV_INSN (core_insn)) = NEXT_INSN (core_insn); |
| if (NEXT_INSN (core_insn)) |
| PREV_INSN (NEXT_INSN (core_insn)) = PREV_INSN (core_insn); |
| |
| /* Re-insert core insn. */ |
| PREV_INSN (core_insn) = PREV_INSN (insn); |
| NEXT_INSN (core_insn) = insn; |
| |
| if (PREV_INSN (core_insn)) |
| NEXT_INSN (PREV_INSN (core_insn)) = core_insn; |
| PREV_INSN (insn) = core_insn; |
| |
| PUT_MODE (core_insn, TImode); |
| PUT_MODE (insn, VOIDmode); |
| } |
| } |
| |
| /* The first insn has TImode, the rest have VOIDmode */ |
| if (GET_MODE (insn) == TImode) |
| PUT_MODE (insn, VOIDmode); |
| else |
| PUT_MODE (insn, BImode); |
| continue; |
| } |
| |
| PUT_MODE (insn, VOIDmode); |
| if (recog_memoized (insn) >= 0 |
| && get_attr_slot (insn) == SLOT_COP) |
| { |
| if (GET_CODE (insn) == JUMP_INSN |
| || ! last |
| || recog_memoized (last) < 0 |
| || get_attr_slot (last) != SLOT_CORE |
| || (get_attr_length (insn) |
| != (TARGET_OPT_VL64 ? 8 : 4) - get_attr_length (last)) |
| || mep_insn_dependent_p (insn, last)) |
| { |
| switch (get_attr_length (insn)) |
| { |
| case 8: |
| break; |
| case 6: |
| insn = mep_make_bundle (gen_nop (), insn); |
| break; |
| case 4: |
| if (TARGET_OPT_VL64) |
| insn = mep_make_bundle (gen_nop32 (), insn); |
| break; |
| case 2: |
| if (TARGET_OPT_VL64) |
| error ("2 byte cop instructions are" |
| " not allowed in 64-bit VLIW mode"); |
| else |
| insn = mep_make_bundle (gen_nop (), insn); |
| break; |
| default: |
| error ("unexpected %d byte cop instruction", |
| get_attr_length (insn)); |
| break; |
| } |
| } |
| else |
| insn = mep_make_bundle (last, insn); |
| } |
| |
| last = insn; |
| } |
| } |
| |
| |
| /* Try to instantiate INTRINSIC with the operands given in OPERANDS. |
| Return true on success. This function can fail if the intrinsic |
| is unavailable or if the operands don't satisfy their predicates. */ |
| |
| bool |
| mep_emit_intrinsic (int intrinsic, const rtx *operands) |
| { |
| const struct cgen_insn *cgen_insn; |
| const struct insn_data_d *idata; |
| rtx newop[10]; |
| int i; |
| |
| if (!mep_get_intrinsic_insn (intrinsic, &cgen_insn)) |
| return false; |
| |
| idata = &insn_data[cgen_insn->icode]; |
| for (i = 0; i < idata->n_operands; i++) |
| { |
| newop[i] = mep_convert_arg (idata->operand[i].mode, operands[i]); |
| if (!idata->operand[i].predicate (newop[i], idata->operand[i].mode)) |
| return false; |
| } |
| |
| emit_insn (idata->genfun (newop[0], newop[1], newop[2], |
| newop[3], newop[4], newop[5], |
| newop[6], newop[7], newop[8])); |
| |
| return true; |
| } |
| |
| |
| /* Apply the given unary intrinsic to OPERANDS[1] and store it on |
| OPERANDS[0]. Report an error if the instruction could not |
| be synthesized. OPERANDS[1] is a register_operand. For sign |
| and zero extensions, it may be smaller than SImode. */ |
| |
| bool |
| mep_expand_unary_intrinsic (int ATTRIBUTE_UNUSED intrinsic, |
| rtx * operands ATTRIBUTE_UNUSED) |
| { |
| return false; |
| } |
| |
| |
| /* Likewise, but apply a binary operation to OPERANDS[1] and |
| OPERANDS[2]. OPERANDS[1] is a register_operand, OPERANDS[2] |
| can be a general_operand. |
| |
| IMMEDIATE and IMMEDIATE3 are intrinsics that take an immediate |
| third operand. REG and REG3 take register operands only. */ |
| |
| bool |
| mep_expand_binary_intrinsic (int ATTRIBUTE_UNUSED immediate, |
| int ATTRIBUTE_UNUSED immediate3, |
| int ATTRIBUTE_UNUSED reg, |
| int ATTRIBUTE_UNUSED reg3, |
| rtx * operands ATTRIBUTE_UNUSED) |
| { |
| return false; |
| } |
| |
| static bool |
| mep_rtx_cost (rtx x, int code, int outer_code ATTRIBUTE_UNUSED, |
| int opno ATTRIBUTE_UNUSED, int *total, |
| bool ATTRIBUTE_UNUSED speed_t) |
| { |
| switch (code) |
| { |
| case CONST_INT: |
| if (INTVAL (x) >= -128 && INTVAL (x) < 127) |
| *total = 0; |
| else if (INTVAL (x) >= -32768 && INTVAL (x) < 65536) |
| *total = 1; |
| else |
| *total = 3; |
| return true; |
| |
| case SYMBOL_REF: |
| *total = optimize_size ? COSTS_N_INSNS (0) : COSTS_N_INSNS (1); |
| return true; |
| |
| case MULT: |
| *total = (GET_CODE (XEXP (x, 1)) == CONST_INT |
| ? COSTS_N_INSNS (3) |
| : COSTS_N_INSNS (2)); |
| return true; |
| } |
| return false; |
| } |
| |
| static int |
| mep_address_cost (rtx addr ATTRIBUTE_UNUSED, |
| enum machine_mode mode ATTRIBUTE_UNUSED, |
| addr_space_t as ATTRIBUTE_UNUSED, |
| bool ATTRIBUTE_UNUSED speed_p) |
| { |
| return 1; |
| } |
| |
| static void |
| mep_asm_init_sections (void) |
| { |
| based_section |
| = get_unnamed_section (SECTION_WRITE, output_section_asm_op, |
| "\t.section .based,\"aw\""); |
| |
| tinybss_section |
| = get_unnamed_section (SECTION_WRITE | SECTION_BSS, output_section_asm_op, |
| "\t.section .sbss,\"aw\""); |
| |
| sdata_section |
| = get_unnamed_section (SECTION_WRITE, output_section_asm_op, |
| "\t.section .sdata,\"aw\",@progbits"); |
| |
| far_section |
| = get_unnamed_section (SECTION_WRITE, output_section_asm_op, |
| "\t.section .far,\"aw\""); |
| |
| farbss_section |
| = get_unnamed_section (SECTION_WRITE | SECTION_BSS, output_section_asm_op, |
| "\t.section .farbss,\"aw\""); |
| |
| frodata_section |
| = get_unnamed_section (0, output_section_asm_op, |
| "\t.section .frodata,\"a\""); |
| |
| srodata_section |
| = get_unnamed_section (0, output_section_asm_op, |
| "\t.section .srodata,\"a\""); |
| |
| vtext_section |
| = get_unnamed_section (SECTION_CODE | SECTION_MEP_VLIW, output_section_asm_op, |
| "\t.section .vtext,\"axv\"\n\t.vliw"); |
| |
| vftext_section |
| = get_unnamed_section (SECTION_CODE | SECTION_MEP_VLIW, output_section_asm_op, |
| "\t.section .vftext,\"axv\"\n\t.vliw"); |
| |
| ftext_section |
| = get_unnamed_section (SECTION_CODE, output_section_asm_op, |
| "\t.section .ftext,\"ax\"\n\t.core"); |
| |
| } |
| |
| /* Initialize the GCC target structure. */ |
| |
| #undef TARGET_ASM_FUNCTION_PROLOGUE |
| #define TARGET_ASM_FUNCTION_PROLOGUE mep_start_function |
| #undef TARGET_ATTRIBUTE_TABLE |
| #define TARGET_ATTRIBUTE_TABLE mep_attribute_table |
| #undef TARGET_COMP_TYPE_ATTRIBUTES |
| #define TARGET_COMP_TYPE_ATTRIBUTES mep_comp_type_attributes |
| #undef TARGET_INSERT_ATTRIBUTES |
| #define TARGET_INSERT_ATTRIBUTES mep_insert_attributes |
| #undef TARGET_FUNCTION_ATTRIBUTE_INLINABLE_P |
| #define TARGET_FUNCTION_ATTRIBUTE_INLINABLE_P mep_function_attribute_inlinable_p |
| #undef TARGET_CAN_INLINE_P |
| #define TARGET_CAN_INLINE_P mep_can_inline_p |
| #undef TARGET_SECTION_TYPE_FLAGS |
| #define TARGET_SECTION_TYPE_FLAGS mep_section_type_flags |
| #undef TARGET_ASM_NAMED_SECTION |
| #define TARGET_ASM_NAMED_SECTION mep_asm_named_section |
| #undef TARGET_INIT_BUILTINS |
| #define TARGET_INIT_BUILTINS mep_init_builtins |
| #undef TARGET_EXPAND_BUILTIN |
| #define TARGET_EXPAND_BUILTIN mep_expand_builtin |
| #undef TARGET_SCHED_ADJUST_COST |
| #define TARGET_SCHED_ADJUST_COST mep_adjust_cost |
| #undef TARGET_SCHED_ISSUE_RATE |
| #define TARGET_SCHED_ISSUE_RATE mep_issue_rate |
| #undef TARGET_SCHED_REORDER |
| #define TARGET_SCHED_REORDER mep_sched_reorder |
| #undef TARGET_STRIP_NAME_ENCODING |
| #define TARGET_STRIP_NAME_ENCODING mep_strip_name_encoding |
| #undef TARGET_ASM_SELECT_SECTION |
| #define TARGET_ASM_SELECT_SECTION mep_select_section |
| #undef TARGET_ASM_UNIQUE_SECTION |
| #define TARGET_ASM_UNIQUE_SECTION mep_unique_section |
| #undef TARGET_ENCODE_SECTION_INFO |
| #define TARGET_ENCODE_SECTION_INFO mep_encode_section_info |
| #undef TARGET_FUNCTION_OK_FOR_SIBCALL |
| #define TARGET_FUNCTION_OK_FOR_SIBCALL mep_function_ok_for_sibcall |
| #undef TARGET_RTX_COSTS |
| #define TARGET_RTX_COSTS mep_rtx_cost |
| #undef TARGET_ADDRESS_COST |
| #define TARGET_ADDRESS_COST mep_address_cost |
| #undef TARGET_MACHINE_DEPENDENT_REORG |
| #define TARGET_MACHINE_DEPENDENT_REORG mep_reorg |
| #undef TARGET_SETUP_INCOMING_VARARGS |
| #define TARGET_SETUP_INCOMING_VARARGS mep_setup_incoming_varargs |
| #undef TARGET_PASS_BY_REFERENCE |
| #define TARGET_PASS_BY_REFERENCE mep_pass_by_reference |
| #undef TARGET_FUNCTION_ARG |
| #define TARGET_FUNCTION_ARG mep_function_arg |
| #undef TARGET_FUNCTION_ARG_ADVANCE |
| #define TARGET_FUNCTION_ARG_ADVANCE mep_function_arg_advance |
| #undef TARGET_VECTOR_MODE_SUPPORTED_P |
| #define TARGET_VECTOR_MODE_SUPPORTED_P mep_vector_mode_supported_p |
| #undef TARGET_OPTION_OVERRIDE |
| #define TARGET_OPTION_OVERRIDE mep_option_override |
| #undef TARGET_ALLOCATE_INITIAL_VALUE |
| #define TARGET_ALLOCATE_INITIAL_VALUE mep_allocate_initial_value |
| #undef TARGET_ASM_INIT_SECTIONS |
| #define TARGET_ASM_INIT_SECTIONS mep_asm_init_sections |
| #undef TARGET_RETURN_IN_MEMORY |
| #define TARGET_RETURN_IN_MEMORY mep_return_in_memory |
| #undef TARGET_NARROW_VOLATILE_BITFIELD |
| #define TARGET_NARROW_VOLATILE_BITFIELD mep_narrow_volatile_bitfield |
| #undef TARGET_EXPAND_BUILTIN_SAVEREGS |
| #define TARGET_EXPAND_BUILTIN_SAVEREGS mep_expand_builtin_saveregs |
| #undef TARGET_BUILD_BUILTIN_VA_LIST |
| #define TARGET_BUILD_BUILTIN_VA_LIST mep_build_builtin_va_list |
| #undef TARGET_EXPAND_BUILTIN_VA_START |
| #define TARGET_EXPAND_BUILTIN_VA_START mep_expand_va_start |
| #undef TARGET_GIMPLIFY_VA_ARG_EXPR |
| #define TARGET_GIMPLIFY_VA_ARG_EXPR mep_gimplify_va_arg_expr |
| #undef TARGET_CAN_ELIMINATE |
| #define TARGET_CAN_ELIMINATE mep_can_eliminate |
| #undef TARGET_CONDITIONAL_REGISTER_USAGE |
| #define TARGET_CONDITIONAL_REGISTER_USAGE mep_conditional_register_usage |
| #undef TARGET_TRAMPOLINE_INIT |
| #define TARGET_TRAMPOLINE_INIT mep_trampoline_init |
| #undef TARGET_LEGITIMATE_CONSTANT_P |
| #define TARGET_LEGITIMATE_CONSTANT_P mep_legitimate_constant_p |
| |
| struct gcc_target targetm = TARGET_INITIALIZER; |
| |
| #include "gt-mep.h" |