| /* Target definitions for the MorphoRISC1 |
| Copyright (C) 2005 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 2, or (at your |
| option) any later version. |
| |
| GCC is distributed in the hope that it will be useful, but WITHOUT |
| ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
| or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public |
| License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GCC; see the file COPYING. If not, write to the Free |
| Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301, USA. */ |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "tm.h" |
| #include "rtl.h" |
| #include "regs.h" |
| #include "hard-reg-set.h" |
| #include "real.h" |
| #include "insn-config.h" |
| #include "conditions.h" |
| #include "insn-attr.h" |
| #include "recog.h" |
| #include "toplev.h" |
| #include "output.h" |
| #include "integrate.h" |
| #include "tree.h" |
| #include "function.h" |
| #include "expr.h" |
| #include "optabs.h" |
| #include "libfuncs.h" |
| #include "flags.h" |
| #include "tm_p.h" |
| #include "ggc.h" |
| #include "insn-flags.h" |
| #include "obstack.h" |
| #include "except.h" |
| #include "target.h" |
| #include "target-def.h" |
| #include "basic-block.h" |
| |
| /* Frame pointer register mask. */ |
| #define FP_MASK (1 << (GPR_FP)) |
| |
| /* Link register mask. */ |
| #define LINK_MASK (1 << (GPR_LINK)) |
| |
| /* Given a SIZE in bytes, advance to the next word. */ |
| #define ROUND_ADVANCE(SIZE) (((SIZE) + UNITS_PER_WORD - 1) / UNITS_PER_WORD) |
| |
| /* A C structure for machine-specific, per-function data. |
| This is added to the cfun structure. */ |
| struct machine_function GTY(()) |
| { |
| /* Flags if __builtin_return_address (n) with n >= 1 was used. */ |
| int ra_needs_full_frame; |
| struct rtx_def * eh_stack_adjust; |
| int interrupt_handler; |
| int has_loops; |
| }; |
| |
| /* Define the information needed to generate branch and scc insns. |
| This is stored from the compare operation. */ |
| struct rtx_def * mt_compare_op0; |
| struct rtx_def * mt_compare_op1; |
| |
| /* Current frame information calculated by compute_frame_size. */ |
| struct mt_frame_info current_frame_info; |
| |
| /* Zero structure to initialize current_frame_info. */ |
| struct mt_frame_info zero_frame_info; |
| |
| /* mt doesn't have unsigned compares need a library call for this. */ |
| struct rtx_def * mt_ucmpsi3_libcall; |
| |
| static int mt_flag_delayed_branch; |
| |
| |
| static rtx |
| mt_struct_value_rtx (tree fndecl ATTRIBUTE_UNUSED, |
| int incoming ATTRIBUTE_UNUSED) |
| { |
| return gen_rtx_REG (Pmode, RETVAL_REGNUM); |
| } |
| |
| /* Implement RETURN_ADDR_RTX. */ |
| rtx |
| mt_return_addr_rtx (int count) |
| { |
| if (count != 0) |
| return NULL_RTX; |
| |
| return get_hard_reg_initial_val (Pmode, GPR_LINK); |
| } |
| |
| /* The following variable value indicates the number of nops required |
| between the current instruction and the next instruction to avoid |
| any pipeline hazards. */ |
| static int mt_nops_required = 0; |
| static const char * mt_nop_reasons = ""; |
| |
| /* Implement ASM_OUTPUT_OPCODE. */ |
| const char * |
| mt_asm_output_opcode (FILE *f ATTRIBUTE_UNUSED, const char *ptr) |
| { |
| if (mt_nops_required) |
| fprintf (f, ";# need %d nops because of %s\n\t", |
| mt_nops_required, mt_nop_reasons); |
| |
| while (mt_nops_required) |
| { |
| fprintf (f, "nop\n\t"); |
| -- mt_nops_required; |
| } |
| |
| return ptr; |
| } |
| |
| /* Given an insn, return whether it's a memory operation or a branch |
| operation, otherwise return TYPE_ARITH. */ |
| static enum attr_type |
| mt_get_attr_type (rtx complete_insn) |
| { |
| rtx insn = PATTERN (complete_insn); |
| |
| if (JUMP_P (complete_insn)) |
| return TYPE_BRANCH; |
| if (CALL_P (complete_insn)) |
| return TYPE_BRANCH; |
| |
| if (GET_CODE (insn) != SET) |
| return TYPE_ARITH; |
| |
| if (SET_DEST (insn) == pc_rtx) |
| return TYPE_BRANCH; |
| |
| if (GET_CODE (SET_DEST (insn)) == MEM) |
| return TYPE_STORE; |
| |
| if (GET_CODE (SET_SRC (insn)) == MEM) |
| return TYPE_LOAD; |
| |
| return TYPE_ARITH; |
| } |
| |
| /* A helper routine for insn_dependent_p called through note_stores. */ |
| |
| static void |
| insn_dependent_p_1 (rtx x, 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 bool |
| insn_dependent_p (rtx x, rtx y) |
| { |
| rtx tmp; |
| |
| if (! INSN_P (x) || ! INSN_P (y)) |
| return 0; |
| |
| tmp = PATTERN (y); |
| note_stores (PATTERN (x), insn_dependent_p_1, &tmp); |
| if (tmp == NULL_RTX) |
| return true; |
| |
| tmp = PATTERN (x); |
| note_stores (PATTERN (y), insn_dependent_p_1, &tmp); |
| return (tmp == NULL_RTX); |
| } |
| |
| |
| /* Return true if anything in insn X is true dependent on anything in |
| insn Y. */ |
| static bool |
| insn_true_dependent_p (rtx x, rtx y) |
| { |
| rtx tmp; |
| |
| if (! INSN_P (x) || ! INSN_P (y)) |
| return 0; |
| |
| tmp = PATTERN (y); |
| note_stores (PATTERN (x), insn_dependent_p_1, &tmp); |
| return (tmp == NULL_RTX); |
| } |
| |
| /* The following determines the number of nops that need to be |
| inserted between the previous instructions and current instruction |
| to avoid pipeline hazards on the mt processor. Remember that |
| the function is not called for asm insns. */ |
| |
| void |
| mt_final_prescan_insn (rtx insn, |
| rtx * opvec ATTRIBUTE_UNUSED, |
| int noperands ATTRIBUTE_UNUSED) |
| { |
| rtx prev_i; |
| enum attr_type prev_attr; |
| |
| mt_nops_required = 0; |
| mt_nop_reasons = ""; |
| |
| /* ms2 constraints are dealt with in reorg. */ |
| if (TARGET_MS2) |
| return; |
| |
| /* Only worry about real instructions. */ |
| if (! INSN_P (insn)) |
| return; |
| |
| /* Find the previous real instructions. */ |
| for (prev_i = PREV_INSN (insn); |
| prev_i != NULL |
| && (! INSN_P (prev_i) |
| || GET_CODE (PATTERN (prev_i)) == USE |
| || GET_CODE (PATTERN (prev_i)) == CLOBBER); |
| prev_i = PREV_INSN (prev_i)) |
| { |
| /* If we meet a barrier, there is no flow through here. */ |
| if (BARRIER_P (prev_i)) |
| return; |
| } |
| |
| /* If there isn't one then there is nothing that we need do. */ |
| if (prev_i == NULL || ! INSN_P (prev_i)) |
| return; |
| |
| prev_attr = mt_get_attr_type (prev_i); |
| |
| /* Delayed branch slots already taken care of by delay branch scheduling. */ |
| if (prev_attr == TYPE_BRANCH) |
| return; |
| |
| switch (mt_get_attr_type (insn)) |
| { |
| case TYPE_LOAD: |
| case TYPE_STORE: |
| /* Avoid consecutive memory operation. */ |
| if ((prev_attr == TYPE_LOAD || prev_attr == TYPE_STORE) |
| && TARGET_MS1_64_001) |
| { |
| mt_nops_required = 1; |
| mt_nop_reasons = "consecutive mem ops"; |
| } |
| /* Drop through. */ |
| |
| case TYPE_ARITH: |
| case TYPE_COMPLEX: |
| /* One cycle of delay is required between load |
| and the dependent arithmetic instruction. */ |
| if (prev_attr == TYPE_LOAD |
| && insn_true_dependent_p (prev_i, insn)) |
| { |
| mt_nops_required = 1; |
| mt_nop_reasons = "load->arith dependency delay"; |
| } |
| break; |
| |
| case TYPE_BRANCH: |
| if (insn_dependent_p (prev_i, insn)) |
| { |
| if (prev_attr == TYPE_ARITH && TARGET_MS1_64_001) |
| { |
| /* One cycle of delay between arith |
| instructions and branch dependent on arith. */ |
| mt_nops_required = 1; |
| mt_nop_reasons = "arith->branch dependency delay"; |
| } |
| else if (prev_attr == TYPE_LOAD) |
| { |
| /* Two cycles of delay are required |
| between load and dependent branch. */ |
| if (TARGET_MS1_64_001) |
| mt_nops_required = 2; |
| else |
| mt_nops_required = 1; |
| mt_nop_reasons = "load->branch dependency delay"; |
| } |
| } |
| break; |
| |
| default: |
| fatal_insn ("mt_final_prescan_insn, invalid insn #1", insn); |
| break; |
| } |
| } |
| |
| /* Print debugging information for a frame. */ |
| static void |
| mt_debug_stack (struct mt_frame_info * info) |
| { |
| int regno; |
| |
| if (!info) |
| { |
| error ("info pointer NULL"); |
| gcc_unreachable (); |
| } |
| |
| fprintf (stderr, "\nStack information for function %s:\n", |
| ((current_function_decl && DECL_NAME (current_function_decl)) |
| ? IDENTIFIER_POINTER (DECL_NAME (current_function_decl)) |
| : "<unknown>")); |
| |
| fprintf (stderr, "\ttotal_size = %d\n", info->total_size); |
| fprintf (stderr, "\tpretend_size = %d\n", info->pretend_size); |
| fprintf (stderr, "\targs_size = %d\n", info->args_size); |
| fprintf (stderr, "\textra_size = %d\n", info->extra_size); |
| fprintf (stderr, "\treg_size = %d\n", info->reg_size); |
| fprintf (stderr, "\tvar_size = %d\n", info->var_size); |
| fprintf (stderr, "\tframe_size = %d\n", info->frame_size); |
| fprintf (stderr, "\treg_mask = 0x%x\n", info->reg_mask); |
| fprintf (stderr, "\tsave_fp = %d\n", info->save_fp); |
| fprintf (stderr, "\tsave_lr = %d\n", info->save_lr); |
| fprintf (stderr, "\tinitialized = %d\n", info->initialized); |
| fprintf (stderr, "\tsaved registers ="); |
| |
| /* Print out reg_mask in a more readable format. */ |
| for (regno = GPR_R0; regno <= GPR_LAST; regno++) |
| if ( (1 << regno) & info->reg_mask) |
| fprintf (stderr, " %s", reg_names[regno]); |
| |
| putc ('\n', stderr); |
| fflush (stderr); |
| } |
| |
| /* Print a memory address as an operand to reference that memory location. */ |
| |
| static void |
| mt_print_operand_simple_address (FILE * file, rtx addr) |
| { |
| if (!addr) |
| error ("PRINT_OPERAND_ADDRESS, null pointer"); |
| |
| else |
| switch (GET_CODE (addr)) |
| { |
| case REG: |
| fprintf (file, "%s, #0", reg_names [REGNO (addr)]); |
| break; |
| |
| case PLUS: |
| { |
| rtx reg = 0; |
| rtx offset = 0; |
| rtx arg0 = XEXP (addr, 0); |
| rtx arg1 = XEXP (addr, 1); |
| |
| if (GET_CODE (arg0) == REG) |
| { |
| reg = arg0; |
| offset = arg1; |
| if (GET_CODE (offset) == REG) |
| fatal_insn ("PRINT_OPERAND_ADDRESS, 2 regs", addr); |
| } |
| |
| else if (GET_CODE (arg1) == REG) |
| reg = arg1, offset = arg0; |
| else if (CONSTANT_P (arg0) && CONSTANT_P (arg1)) |
| { |
| fprintf (file, "%s, #", reg_names [GPR_R0]); |
| output_addr_const (file, addr); |
| break; |
| } |
| fprintf (file, "%s, #", reg_names [REGNO (reg)]); |
| output_addr_const (file, offset); |
| break; |
| } |
| |
| case LABEL_REF: |
| case SYMBOL_REF: |
| case CONST_INT: |
| case CONST: |
| output_addr_const (file, addr); |
| break; |
| |
| default: |
| fatal_insn ("PRINT_OPERAND_ADDRESS, invalid insn #1", addr); |
| break; |
| } |
| } |
| |
| /* Implement PRINT_OPERAND_ADDRESS. */ |
| void |
| mt_print_operand_address (FILE * file, rtx addr) |
| { |
| if (GET_CODE (addr) == AND |
| && GET_CODE (XEXP (addr, 1)) == CONST_INT |
| && INTVAL (XEXP (addr, 1)) == -3) |
| mt_print_operand_simple_address (file, XEXP (addr, 0)); |
| else |
| mt_print_operand_simple_address (file, addr); |
| } |
| |
| /* Implement PRINT_OPERAND. */ |
| void |
| mt_print_operand (FILE * file, rtx x, int code) |
| { |
| switch (code) |
| { |
| case '#': |
| /* Output a nop if there's nothing for the delay slot. */ |
| if (dbr_sequence_length () == 0) |
| fputs ("\n\tnop", file); |
| return; |
| |
| case 'H': |
| fprintf(file, "#%%hi16("); |
| output_addr_const (file, x); |
| fprintf(file, ")"); |
| return; |
| |
| case 'L': |
| fprintf(file, "#%%lo16("); |
| output_addr_const (file, x); |
| fprintf(file, ")"); |
| return; |
| |
| case 'N': |
| fprintf(file, "#%ld", ~INTVAL (x)); |
| return; |
| |
| case 'z': |
| if (GET_CODE (x) == CONST_INT && INTVAL (x) == 0) |
| { |
| fputs (reg_names[GPR_R0], file); |
| return; |
| } |
| |
| case 0: |
| /* Handled below. */ |
| break; |
| |
| default: |
| /* output_operand_lossage ("mt_print_operand: unknown code"); */ |
| fprintf (file, "unknown code"); |
| return; |
| } |
| |
| switch (GET_CODE (x)) |
| { |
| case REG: |
| fputs (reg_names [REGNO (x)], file); |
| break; |
| |
| case CONST: |
| case CONST_INT: |
| fprintf(file, "#%ld", INTVAL (x)); |
| break; |
| |
| case MEM: |
| mt_print_operand_address(file, XEXP (x,0)); |
| break; |
| |
| case LABEL_REF: |
| case SYMBOL_REF: |
| output_addr_const (file, x); |
| break; |
| |
| default: |
| fprintf(file, "Uknown code: %d", GET_CODE (x)); |
| break; |
| } |
| |
| return; |
| } |
| |
| /* Implement INIT_CUMULATIVE_ARGS. */ |
| void |
| mt_init_cumulative_args (CUMULATIVE_ARGS * cum, tree fntype, rtx libname, |
| tree fndecl ATTRIBUTE_UNUSED, int incoming) |
| { |
| *cum = 0; |
| |
| if (TARGET_DEBUG_ARG) |
| { |
| fprintf (stderr, "\nmt_init_cumulative_args:"); |
| |
| if (incoming) |
| fputs (" incoming", stderr); |
| |
| if (fntype) |
| { |
| tree ret_type = TREE_TYPE (fntype); |
| fprintf (stderr, " return = %s,", |
| tree_code_name[ (int)TREE_CODE (ret_type) ]); |
| } |
| |
| if (libname && GET_CODE (libname) == SYMBOL_REF) |
| fprintf (stderr, " libname = %s", XSTR (libname, 0)); |
| |
| if (cfun->returns_struct) |
| fprintf (stderr, " return-struct"); |
| |
| putc ('\n', stderr); |
| } |
| } |
| |
| /* Compute the slot number to pass an argument in. |
| Returns the slot number or -1 if passing on the stack. |
| |
| CUM is a variable of type CUMULATIVE_ARGS which gives info about |
| the preceding args and about the function being called. |
| MODE is the argument's machine mode. |
| TYPE is the data type of the argument (as a tree). |
| This is null for libcalls where that information may |
| not be available. |
| NAMED is nonzero if this argument is a named parameter |
| (otherwise it is an extra parameter matching an ellipsis). |
| INCOMING_P is zero for FUNCTION_ARG, nonzero for FUNCTION_INCOMING_ARG. |
| *PREGNO records the register number to use if scalar type. */ |
| |
| static int |
| mt_function_arg_slotno (const CUMULATIVE_ARGS * cum, |
| enum machine_mode mode, |
| tree type, |
| int named ATTRIBUTE_UNUSED, |
| int incoming_p ATTRIBUTE_UNUSED, |
| int * pregno) |
| { |
| int regbase = FIRST_ARG_REGNUM; |
| int slotno = * cum; |
| |
| if (mode == VOIDmode || targetm.calls.must_pass_in_stack (mode, type)) |
| return -1; |
| |
| if (slotno >= MT_NUM_ARG_REGS) |
| return -1; |
| |
| * pregno = regbase + slotno; |
| |
| return slotno; |
| } |
| |
| /* Implement FUNCTION_ARG. */ |
| rtx |
| mt_function_arg (const CUMULATIVE_ARGS * cum, |
| enum machine_mode mode, |
| tree type, |
| int named, |
| int incoming_p) |
| { |
| int slotno, regno; |
| rtx reg; |
| |
| slotno = mt_function_arg_slotno (cum, mode, type, named, incoming_p, ®no); |
| |
| if (slotno == -1) |
| reg = NULL_RTX; |
| else |
| reg = gen_rtx_REG (mode, regno); |
| |
| return reg; |
| } |
| |
| /* Implement FUNCTION_ARG_ADVANCE. */ |
| void |
| mt_function_arg_advance (CUMULATIVE_ARGS * cum, |
| enum machine_mode mode, |
| tree type ATTRIBUTE_UNUSED, |
| int named) |
| { |
| int slotno, regno; |
| |
| /* We pass 0 for incoming_p here, it doesn't matter. */ |
| slotno = mt_function_arg_slotno (cum, mode, type, named, 0, ®no); |
| |
| * cum += (mode != BLKmode |
| ? ROUND_ADVANCE (GET_MODE_SIZE (mode)) |
| : ROUND_ADVANCE (int_size_in_bytes (type))); |
| |
| if (TARGET_DEBUG_ARG) |
| fprintf (stderr, |
| "mt_function_arg_advance: words = %2d, mode = %4s, named = %d, size = %3d\n", |
| *cum, GET_MODE_NAME (mode), named, |
| (*cum) * UNITS_PER_WORD); |
| } |
| |
| /* Implement hook TARGET_ARG_PARTIAL_BYTES. |
| |
| Returns the number of bytes at the beginning of an argument that |
| must be put in registers. The value must be zero for arguments |
| that are passed entirely in registers or that are entirely pushed |
| on the stack. */ |
| static int |
| mt_arg_partial_bytes (CUMULATIVE_ARGS * pcum, |
| enum machine_mode mode, |
| tree type, |
| bool named ATTRIBUTE_UNUSED) |
| { |
| int cum = * pcum; |
| int words; |
| |
| if (mode == BLKmode) |
| words = ((int_size_in_bytes (type) + UNITS_PER_WORD - 1) |
| / UNITS_PER_WORD); |
| else |
| words = (GET_MODE_SIZE (mode) + UNITS_PER_WORD - 1) / UNITS_PER_WORD; |
| |
| if (! targetm.calls.pass_by_reference (&cum, mode, type, named) |
| && cum < MT_NUM_ARG_REGS |
| && (cum + words) > MT_NUM_ARG_REGS) |
| { |
| int bytes = (MT_NUM_ARG_REGS - cum) * UNITS_PER_WORD; |
| |
| if (TARGET_DEBUG) |
| fprintf (stderr, "function_arg_partial_nregs = %d\n", bytes); |
| return bytes; |
| } |
| |
| return 0; |
| } |
| |
| |
| /* Implement TARGET_PASS_BY_REFERENCE hook. */ |
| static bool |
| mt_pass_by_reference (CUMULATIVE_ARGS * cum ATTRIBUTE_UNUSED, |
| enum machine_mode mode ATTRIBUTE_UNUSED, |
| tree type, |
| bool named ATTRIBUTE_UNUSED) |
| { |
| return (type && int_size_in_bytes (type) > 4 * UNITS_PER_WORD); |
| } |
| |
| /* Implement FUNCTION_ARG_BOUNDARY. */ |
| int |
| mt_function_arg_boundary (enum machine_mode mode ATTRIBUTE_UNUSED, |
| tree type ATTRIBUTE_UNUSED) |
| { |
| return BITS_PER_WORD; |
| } |
| |
| /* Implement REG_OK_FOR_BASE_P. */ |
| int |
| mt_reg_ok_for_base_p (rtx x, int strict) |
| { |
| if (strict) |
| return (((unsigned) REGNO (x)) < FIRST_PSEUDO_REGISTER); |
| return 1; |
| } |
| |
| /* Helper function of mt_legitimate_address_p. Return true if XINSN |
| is a simple address, otherwise false. */ |
| static bool |
| mt_legitimate_simple_address_p (enum machine_mode mode ATTRIBUTE_UNUSED, |
| rtx xinsn, int strict) |
| { |
| if (TARGET_DEBUG) |
| { |
| fprintf (stderr, "\n========== GO_IF_LEGITIMATE_ADDRESS, %sstrict\n", |
| strict ? "" : "not "); |
| debug_rtx (xinsn); |
| } |
| |
| if (GET_CODE (xinsn) == REG && mt_reg_ok_for_base_p (xinsn, strict)) |
| return true; |
| |
| if (GET_CODE (xinsn) == PLUS |
| && GET_CODE (XEXP (xinsn, 0)) == REG |
| && mt_reg_ok_for_base_p (XEXP (xinsn, 0), strict) |
| && GET_CODE (XEXP (xinsn, 1)) == CONST_INT |
| && SMALL_INT (XEXP (xinsn, 1))) |
| return true; |
| |
| return false; |
| } |
| |
| |
| /* Helper function of GO_IF_LEGITIMATE_ADDRESS. Return nonzero if |
| XINSN is a legitimate address on MT. */ |
| int |
| mt_legitimate_address_p (enum machine_mode mode, rtx xinsn, int strict) |
| { |
| if (mt_legitimate_simple_address_p (mode, xinsn, strict)) |
| return 1; |
| |
| if ((mode) == SImode |
| && GET_CODE (xinsn) == AND |
| && GET_CODE (XEXP (xinsn, 1)) == CONST_INT |
| && INTVAL (XEXP (xinsn, 1)) == -3) |
| return mt_legitimate_simple_address_p (mode, XEXP (xinsn, 0), strict); |
| else |
| return 0; |
| } |
| |
| /* Return truth value of whether OP can be used as an operands where a |
| register or 16 bit unsigned integer is needed. */ |
| |
| int |
| uns_arith_operand (rtx op, enum machine_mode mode) |
| { |
| if (GET_CODE (op) == CONST_INT && SMALL_INT_UNSIGNED (op)) |
| return 1; |
| |
| return register_operand (op, mode); |
| } |
| |
| /* Return truth value of whether OP can be used as an operands where a |
| 16 bit integer is needed. */ |
| |
| int |
| arith_operand (rtx op, enum machine_mode mode) |
| { |
| if (GET_CODE (op) == CONST_INT && SMALL_INT (op)) |
| return 1; |
| |
| return register_operand (op, mode); |
| } |
| |
| /* Return truth value of whether OP is a register or the constant 0. */ |
| |
| int |
| reg_or_0_operand (rtx op, enum machine_mode mode) |
| { |
| switch (GET_CODE (op)) |
| { |
| case CONST_INT: |
| return INTVAL (op) == 0; |
| |
| case REG: |
| case SUBREG: |
| return register_operand (op, mode); |
| |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| /* Return truth value of whether OP is a constant that requires two |
| loads to put in a register. */ |
| |
| int |
| big_const_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| if (GET_CODE (op) == CONST_INT && CONST_OK_FOR_LETTER_P (INTVAL (op), 'M')) |
| return 1; |
| |
| return 0; |
| } |
| |
| /* Return truth value of whether OP is a constant that require only |
| one load to put in a register. */ |
| |
| int |
| single_const_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| if (big_const_operand (op, mode) |
| || GET_CODE (op) == CONST |
| || GET_CODE (op) == LABEL_REF |
| || GET_CODE (op) == SYMBOL_REF) |
| return 0; |
| |
| return 1; |
| } |
| |
| /* True if the current function is an interrupt handler |
| (either via #pragma or an attribute specification). */ |
| int interrupt_handler; |
| enum processor_type mt_cpu; |
| |
| static struct machine_function * |
| mt_init_machine_status (void) |
| { |
| struct machine_function *f; |
| |
| f = ggc_alloc_cleared (sizeof (struct machine_function)); |
| |
| return f; |
| } |
| |
| /* Implement OVERRIDE_OPTIONS. */ |
| void |
| mt_override_options (void) |
| { |
| if (mt_cpu_string != NULL) |
| { |
| if (!strcmp (mt_cpu_string, "ms1-64-001")) |
| mt_cpu = PROCESSOR_MS1_64_001; |
| else if (!strcmp (mt_cpu_string, "ms1-16-002")) |
| mt_cpu = PROCESSOR_MS1_16_002; |
| else if (!strcmp (mt_cpu_string, "ms1-16-003")) |
| mt_cpu = PROCESSOR_MS1_16_003; |
| else if (!strcmp (mt_cpu_string, "ms2")) |
| mt_cpu = PROCESSOR_MS2; |
| else |
| error ("bad value (%s) for -march= switch", mt_cpu_string); |
| } |
| else |
| mt_cpu = PROCESSOR_MS1_16_002; |
| |
| if (flag_exceptions) |
| { |
| flag_omit_frame_pointer = 0; |
| flag_gcse = 0; |
| } |
| |
| /* We do delayed branch filling in machine dependent reorg */ |
| mt_flag_delayed_branch = flag_delayed_branch; |
| flag_delayed_branch = 0; |
| |
| init_machine_status = mt_init_machine_status; |
| } |
| |
| /* Do what is necessary for `va_start'. We look at the current function |
| to determine if stdarg or varargs is used and return the address of the |
| first unnamed parameter. */ |
| |
| static void |
| mt_setup_incoming_varargs (CUMULATIVE_ARGS *cum, |
| enum machine_mode mode ATTRIBUTE_UNUSED, |
| tree type ATTRIBUTE_UNUSED, |
| int *pretend_size, int no_rtl) |
| { |
| int regno; |
| int regs = MT_NUM_ARG_REGS - *cum; |
| |
| *pretend_size = regs < 0 ? 0 : GET_MODE_SIZE (SImode) * regs; |
| |
| if (no_rtl) |
| return; |
| |
| for (regno = *cum; regno < MT_NUM_ARG_REGS; regno++) |
| { |
| rtx reg = gen_rtx_REG (SImode, FIRST_ARG_REGNUM + regno); |
| rtx slot = gen_rtx_PLUS (Pmode, |
| gen_rtx_REG (SImode, ARG_POINTER_REGNUM), |
| GEN_INT (UNITS_PER_WORD * regno)); |
| |
| emit_move_insn (gen_rtx_MEM (SImode, slot), reg); |
| } |
| } |
| |
| /* Returns the number of bytes offset between the frame pointer and the stack |
| pointer for the current function. SIZE is the number of bytes of space |
| needed for local variables. */ |
| |
| unsigned int |
| mt_compute_frame_size (int size) |
| { |
| int regno; |
| unsigned int total_size; |
| unsigned int var_size; |
| unsigned int args_size; |
| unsigned int pretend_size; |
| unsigned int extra_size; |
| unsigned int reg_size; |
| unsigned int frame_size; |
| unsigned int reg_mask; |
| |
| var_size = size; |
| args_size = current_function_outgoing_args_size; |
| pretend_size = current_function_pretend_args_size; |
| extra_size = FIRST_PARM_OFFSET (0); |
| total_size = extra_size + pretend_size + args_size + var_size; |
| reg_size = 0; |
| reg_mask = 0; |
| |
| /* Calculate space needed for registers. */ |
| for (regno = GPR_R0; regno <= GPR_LAST; regno++) |
| { |
| if (MUST_SAVE_REGISTER (regno)) |
| { |
| reg_size += UNITS_PER_WORD; |
| reg_mask |= 1 << regno; |
| } |
| } |
| |
| current_frame_info.save_fp = (regs_ever_live [GPR_FP] |
| || frame_pointer_needed |
| || interrupt_handler); |
| current_frame_info.save_lr = (regs_ever_live [GPR_LINK] |
| || profile_flag |
| || interrupt_handler); |
| |
| reg_size += (current_frame_info.save_fp + current_frame_info.save_lr) |
| * UNITS_PER_WORD; |
| total_size += reg_size; |
| total_size = ((total_size + 3) & ~3); |
| |
| frame_size = total_size; |
| |
| /* Save computed information. */ |
| current_frame_info.pretend_size = pretend_size; |
| current_frame_info.var_size = var_size; |
| current_frame_info.args_size = args_size; |
| current_frame_info.reg_size = reg_size; |
| current_frame_info.frame_size = args_size + var_size; |
| current_frame_info.total_size = total_size; |
| current_frame_info.extra_size = extra_size; |
| current_frame_info.reg_mask = reg_mask; |
| current_frame_info.initialized = reload_completed; |
| |
| return total_size; |
| } |
| |
| /* Emit code to save REG in stack offset pointed to by MEM. |
| STACK_OFFSET is the offset from the SP where the save will happen. |
| This function sets the REG_FRAME_RELATED_EXPR note accordingly. */ |
| static void |
| mt_emit_save_restore (enum save_direction direction, |
| rtx reg, rtx mem, int stack_offset) |
| { |
| if (direction == FROM_PROCESSOR_TO_MEM) |
| { |
| rtx insn; |
| |
| insn = emit_move_insn (mem, reg); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| REG_NOTES (insn) |
| = gen_rtx_EXPR_LIST |
| (REG_FRAME_RELATED_EXPR, |
| gen_rtx_SET (VOIDmode, |
| gen_rtx_MEM (SImode, |
| gen_rtx_PLUS (SImode, |
| stack_pointer_rtx, |
| GEN_INT (stack_offset))), |
| reg), |
| REG_NOTES (insn)); |
| } |
| else |
| emit_move_insn (reg, mem); |
| } |
| |
| |
| /* Emit code to save the frame pointer in the prologue and restore |
| frame pointer in epilogue. */ |
| |
| static void |
| mt_emit_save_fp (enum save_direction direction, |
| struct mt_frame_info info) |
| { |
| rtx base_reg; |
| int reg_mask = info.reg_mask & ~(FP_MASK | LINK_MASK); |
| int offset = info.total_size; |
| int stack_offset = info.total_size; |
| |
| /* If there is nothing to save, get out now. */ |
| if (! info.save_fp && ! info.save_lr && ! reg_mask) |
| return; |
| |
| /* If offset doesn't fit in a 15-bit signed integer, |
| uses a scratch registers to get a smaller offset. */ |
| if (CONST_OK_FOR_LETTER_P(offset, 'O')) |
| base_reg = stack_pointer_rtx; |
| else |
| { |
| /* Use the scratch register R9 that holds old stack pointer. */ |
| base_reg = gen_rtx_REG (SImode, GPR_R9); |
| offset = 0; |
| } |
| |
| if (info.save_fp) |
| { |
| offset -= UNITS_PER_WORD; |
| stack_offset -= UNITS_PER_WORD; |
| mt_emit_save_restore |
| (direction, gen_rtx_REG (SImode, GPR_FP), |
| gen_rtx_MEM (SImode, |
| gen_rtx_PLUS (SImode, base_reg, GEN_INT (offset))), |
| stack_offset); |
| } |
| } |
| |
| /* Emit code to save registers in the prologue and restore register |
| in epilogue. */ |
| |
| static void |
| mt_emit_save_regs (enum save_direction direction, |
| struct mt_frame_info info) |
| { |
| rtx base_reg; |
| int regno; |
| int reg_mask = info.reg_mask & ~(FP_MASK | LINK_MASK); |
| int offset = info.total_size; |
| int stack_offset = info.total_size; |
| |
| /* If there is nothing to save, get out now. */ |
| if (! info.save_fp && ! info.save_lr && ! reg_mask) |
| return; |
| |
| /* If offset doesn't fit in a 15-bit signed integer, |
| uses a scratch registers to get a smaller offset. */ |
| if (CONST_OK_FOR_LETTER_P(offset, 'O')) |
| base_reg = stack_pointer_rtx; |
| else |
| { |
| /* Use the scratch register R9 that holds old stack pointer. */ |
| base_reg = gen_rtx_REG (SImode, GPR_R9); |
| offset = 0; |
| } |
| |
| if (info.save_fp) |
| { |
| /* This just records the space for it, the actual move generated in |
| mt_emit_save_fp (). */ |
| offset -= UNITS_PER_WORD; |
| stack_offset -= UNITS_PER_WORD; |
| } |
| |
| if (info.save_lr) |
| { |
| offset -= UNITS_PER_WORD; |
| stack_offset -= UNITS_PER_WORD; |
| mt_emit_save_restore |
| (direction, gen_rtx_REG (SImode, GPR_LINK), |
| gen_rtx_MEM (SImode, |
| gen_rtx_PLUS (SImode, base_reg, GEN_INT (offset))), |
| stack_offset); |
| } |
| |
| /* Save any needed call-saved regs. */ |
| for (regno = GPR_R0; regno <= GPR_LAST; regno++) |
| { |
| if ((reg_mask & (1 << regno)) != 0) |
| { |
| offset -= UNITS_PER_WORD; |
| stack_offset -= UNITS_PER_WORD; |
| mt_emit_save_restore |
| (direction, gen_rtx_REG (SImode, regno), |
| gen_rtx_MEM (SImode, |
| gen_rtx_PLUS (SImode, base_reg, GEN_INT (offset))), |
| stack_offset); |
| } |
| } |
| } |
| |
| /* Return true if FUNC is a function with the 'interrupt' attribute. */ |
| static bool |
| mt_interrupt_function_p (tree func) |
| { |
| tree a; |
| |
| if (TREE_CODE (func) != FUNCTION_DECL) |
| return false; |
| |
| a = lookup_attribute ("interrupt", DECL_ATTRIBUTES (func)); |
| return a != NULL_TREE; |
| } |
| |
| /* Generate prologue code. */ |
| void |
| mt_expand_prologue (void) |
| { |
| rtx size_rtx, insn; |
| unsigned int frame_size; |
| |
| if (mt_interrupt_function_p (current_function_decl)) |
| { |
| interrupt_handler = 1; |
| if (cfun->machine) |
| cfun->machine->interrupt_handler = 1; |
| } |
| |
| mt_compute_frame_size (get_frame_size ()); |
| |
| if (TARGET_DEBUG_STACK) |
| mt_debug_stack (¤t_frame_info); |
| |
| /* Compute size of stack adjustment. */ |
| frame_size = current_frame_info.total_size; |
| |
| /* If offset doesn't fit in a 15-bit signed integer, |
| uses a scratch registers to get a smaller offset. */ |
| if (CONST_OK_FOR_LETTER_P(frame_size, 'O')) |
| size_rtx = GEN_INT (frame_size); |
| else |
| { |
| /* We do not have any scratch registers. */ |
| gcc_assert (!interrupt_handler); |
| |
| size_rtx = gen_rtx_REG (SImode, GPR_R9); |
| insn = emit_move_insn (size_rtx, GEN_INT (frame_size & 0xffff0000)); |
| insn = emit_insn (gen_iorsi3 (size_rtx, size_rtx, |
| GEN_INT (frame_size & 0x0000ffff))); |
| } |
| |
| /* Allocate stack for this frame. */ |
| /* Make stack adjustment and use scratch register if constant too |
| large to fit as immediate. */ |
| if (frame_size) |
| { |
| insn = emit_insn (gen_subsi3 (stack_pointer_rtx, |
| stack_pointer_rtx, |
| size_rtx)); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| REG_NOTES (insn) |
| = gen_rtx_EXPR_LIST (REG_FRAME_RELATED_EXPR, |
| gen_rtx_SET (VOIDmode, |
| stack_pointer_rtx, |
| gen_rtx_MINUS (SImode, |
| stack_pointer_rtx, |
| GEN_INT (frame_size))), |
| REG_NOTES (insn)); |
| } |
| |
| /* Set R9 to point to old sp if required for access to register save |
| area. */ |
| if ( current_frame_info.reg_size != 0 |
| && !CONST_OK_FOR_LETTER_P (frame_size, 'O')) |
| emit_insn (gen_addsi3 (size_rtx, size_rtx, stack_pointer_rtx)); |
| |
| /* Save the frame pointer. */ |
| mt_emit_save_fp (FROM_PROCESSOR_TO_MEM, current_frame_info); |
| |
| /* Now put the frame pointer into the frame pointer register. */ |
| if (frame_pointer_needed) |
| { |
| insn = emit_move_insn (frame_pointer_rtx, stack_pointer_rtx); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| } |
| |
| /* Save the registers. */ |
| mt_emit_save_regs (FROM_PROCESSOR_TO_MEM, current_frame_info); |
| |
| /* If we are profiling, make sure no instructions are scheduled before |
| the call to mcount. */ |
| if (profile_flag) |
| emit_insn (gen_blockage ()); |
| } |
| |
| /* Implement EPILOGUE_USES. */ |
| int |
| mt_epilogue_uses (int regno) |
| { |
| if (cfun->machine && cfun->machine->interrupt_handler && reload_completed) |
| return 1; |
| return regno == GPR_LINK; |
| } |
| |
| /* Generate epilogue. EH_MODE is NORMAL_EPILOGUE when generating a |
| function epilogue, or EH_EPILOGUE when generating an EH |
| epilogue. */ |
| void |
| mt_expand_epilogue (enum epilogue_type eh_mode) |
| { |
| rtx size_rtx, insn; |
| unsigned frame_size; |
| |
| mt_compute_frame_size (get_frame_size ()); |
| |
| if (TARGET_DEBUG_STACK) |
| mt_debug_stack (& current_frame_info); |
| |
| /* Compute size of stack adjustment. */ |
| frame_size = current_frame_info.total_size; |
| |
| /* If offset doesn't fit in a 15-bit signed integer, |
| uses a scratch registers to get a smaller offset. */ |
| if (CONST_OK_FOR_LETTER_P(frame_size, 'O')) |
| size_rtx = GEN_INT (frame_size); |
| else |
| { |
| /* We do not have any scratch registers. */ |
| gcc_assert (!interrupt_handler); |
| |
| size_rtx = gen_rtx_REG (SImode, GPR_R9); |
| insn = emit_move_insn (size_rtx, GEN_INT (frame_size & 0xffff0000)); |
| insn = emit_insn (gen_iorsi3 (size_rtx, size_rtx, |
| GEN_INT (frame_size & 0x0000ffff))); |
| /* Set R9 to point to old sp if required for access to register |
| save area. */ |
| emit_insn (gen_addsi3 (size_rtx, size_rtx, stack_pointer_rtx)); |
| } |
| |
| /* Restore sp if there was some possible change to it. */ |
| if (frame_pointer_needed) |
| insn = emit_move_insn (stack_pointer_rtx, frame_pointer_rtx); |
| |
| /* Restore the registers. */ |
| mt_emit_save_fp (FROM_MEM_TO_PROCESSOR, current_frame_info); |
| mt_emit_save_regs (FROM_MEM_TO_PROCESSOR, current_frame_info); |
| |
| /* Make stack adjustment and use scratch register if constant too |
| large to fit as immediate. */ |
| if (frame_size) |
| { |
| if (CONST_OK_FOR_LETTER_P(frame_size, 'O')) |
| /* Can handle this with simple add. */ |
| insn = emit_insn (gen_addsi3 (stack_pointer_rtx, |
| stack_pointer_rtx, |
| size_rtx)); |
| else |
| /* Scratch reg R9 has the old sp value. */ |
| insn = emit_move_insn (stack_pointer_rtx, |
| gen_rtx_REG (SImode, GPR_R9)); |
| |
| REG_NOTES (insn) |
| = gen_rtx_EXPR_LIST (REG_FRAME_RELATED_EXPR, |
| gen_rtx_SET (VOIDmode, |
| stack_pointer_rtx, |
| gen_rtx_PLUS (SImode, |
| stack_pointer_rtx, |
| GEN_INT (frame_size))), |
| REG_NOTES (insn)); |
| } |
| |
| if (cfun->machine && cfun->machine->eh_stack_adjust != NULL_RTX) |
| /* Perform the additional bump for __throw. */ |
| emit_insn (gen_addsi3 (stack_pointer_rtx, |
| stack_pointer_rtx, |
| cfun->machine->eh_stack_adjust)); |
| |
| /* Generate the appropriate return. */ |
| if (eh_mode == EH_EPILOGUE) |
| { |
| emit_jump_insn (gen_eh_return_internal ()); |
| emit_barrier (); |
| } |
| else if (interrupt_handler) |
| emit_jump_insn (gen_return_interrupt_internal ()); |
| else |
| emit_jump_insn (gen_return_internal ()); |
| |
| /* Reset state info for each function. */ |
| interrupt_handler = 0; |
| current_frame_info = zero_frame_info; |
| if (cfun->machine) |
| cfun->machine->eh_stack_adjust = NULL_RTX; |
| } |
| |
| |
| /* Generate code for the "eh_return" pattern. */ |
| void |
| mt_expand_eh_return (rtx * operands) |
| { |
| if (GET_CODE (operands[0]) != REG |
| || REGNO (operands[0]) != EH_RETURN_STACKADJ_REGNO) |
| { |
| rtx sp = EH_RETURN_STACKADJ_RTX; |
| |
| emit_move_insn (sp, operands[0]); |
| operands[0] = sp; |
| } |
| |
| emit_insn (gen_eh_epilogue (operands[0])); |
| } |
| |
| /* Generate code for the "eh_epilogue" pattern. */ |
| void |
| mt_emit_eh_epilogue (rtx * operands ATTRIBUTE_UNUSED) |
| { |
| cfun->machine->eh_stack_adjust = EH_RETURN_STACKADJ_RTX; /* operands[0]; */ |
| mt_expand_epilogue (EH_EPILOGUE); |
| } |
| |
| /* Handle an "interrupt" attribute. */ |
| static tree |
| mt_handle_interrupt_attribute (tree * node, |
| tree name, |
| tree args ATTRIBUTE_UNUSED, |
| int flags ATTRIBUTE_UNUSED, |
| bool * no_add_attrs) |
| { |
| if (TREE_CODE (*node) != FUNCTION_DECL) |
| { |
| warning (OPT_Wattributes, |
| "%qs attribute only applies to functions", |
| IDENTIFIER_POINTER (name)); |
| *no_add_attrs = true; |
| } |
| |
| return NULL_TREE; |
| } |
| |
| /* Table of machine attributes. */ |
| const struct attribute_spec mt_attribute_table[] = |
| { |
| /* name, min, max, decl?, type?, func?, handler */ |
| { "interrupt", 0, 0, false, false, false, mt_handle_interrupt_attribute }, |
| { NULL, 0, 0, false, false, false, NULL } |
| }; |
| |
| /* Implement INITIAL_ELIMINATION_OFFSET. */ |
| int |
| mt_initial_elimination_offset (int from, int to) |
| { |
| mt_compute_frame_size (get_frame_size ()); |
| |
| if (from == FRAME_POINTER_REGNUM && to == STACK_POINTER_REGNUM) |
| return 0; |
| |
| else if (from == ARG_POINTER_REGNUM && to == STACK_POINTER_REGNUM) |
| return current_frame_info.total_size; |
| |
| else if (from == ARG_POINTER_REGNUM && to == FRAME_POINTER_REGNUM) |
| return current_frame_info.total_size; |
| |
| else |
| gcc_unreachable (); |
| } |
| |
| /* Generate a compare for CODE. Return a brand-new rtx that |
| represents the result of the compare. */ |
| |
| static rtx |
| mt_generate_compare (enum rtx_code code, rtx op0, rtx op1) |
| { |
| rtx scratch0, scratch1, const_scratch; |
| |
| switch (code) |
| { |
| case GTU: |
| case LTU: |
| case GEU: |
| case LEU: |
| /* Need to adjust ranges for faking unsigned compares. */ |
| scratch0 = gen_reg_rtx (SImode); |
| scratch1 = gen_reg_rtx (SImode); |
| const_scratch = force_reg (SImode, GEN_INT(MT_MIN_INT)); |
| emit_insn (gen_addsi3 (scratch0, const_scratch, op0)); |
| emit_insn (gen_addsi3 (scratch1, const_scratch, op1)); |
| break; |
| default: |
| scratch0 = op0; |
| scratch1 = op1; |
| break; |
| } |
| |
| /* Adjust compare operator to fake unsigned compares. */ |
| switch (code) |
| { |
| case GTU: |
| code = GT; break; |
| case LTU: |
| code = LT; break; |
| case GEU: |
| code = GE; break; |
| case LEU: |
| code = LE; break; |
| default: |
| /* do nothing */ |
| break; |
| } |
| |
| /* Generate the actual compare. */ |
| return gen_rtx_fmt_ee (code, VOIDmode, scratch0, scratch1); |
| } |
| |
| /* Emit a branch of kind CODE to location LOC. */ |
| |
| void |
| mt_emit_cbranch (enum rtx_code code, rtx loc, rtx op0, rtx op1) |
| { |
| rtx condition_rtx, loc_ref; |
| |
| if (! reg_or_0_operand (op0, SImode)) |
| op0 = copy_to_mode_reg (SImode, op0); |
| |
| if (! reg_or_0_operand (op1, SImode)) |
| op1 = copy_to_mode_reg (SImode, op1); |
| |
| condition_rtx = mt_generate_compare (code, op0, op1); |
| loc_ref = gen_rtx_LABEL_REF (VOIDmode, loc); |
| emit_jump_insn (gen_rtx_SET (VOIDmode, pc_rtx, |
| gen_rtx_IF_THEN_ELSE (VOIDmode, condition_rtx, |
| loc_ref, pc_rtx))); |
| } |
| |
| /* Subfunction of the following function. Update the flags of any MEM |
| found in part of X. */ |
| |
| static void |
| mt_set_memflags_1 (rtx x, int in_struct_p, int volatile_p) |
| { |
| int i; |
| |
| switch (GET_CODE (x)) |
| { |
| case SEQUENCE: |
| case PARALLEL: |
| for (i = XVECLEN (x, 0) - 1; i >= 0; i--) |
| mt_set_memflags_1 (XVECEXP (x, 0, i), in_struct_p, volatile_p); |
| break; |
| |
| case INSN: |
| mt_set_memflags_1 (PATTERN (x), in_struct_p, volatile_p); |
| break; |
| |
| case SET: |
| mt_set_memflags_1 (SET_DEST (x), in_struct_p, volatile_p); |
| mt_set_memflags_1 (SET_SRC (x), in_struct_p, volatile_p); |
| break; |
| |
| case MEM: |
| MEM_IN_STRUCT_P (x) = in_struct_p; |
| MEM_VOLATILE_P (x) = volatile_p; |
| /* Sadly, we cannot use alias sets because the extra aliasing |
| produced by the AND interferes. Given that two-byte quantities |
| are the only thing we would be able to differentiate anyway, |
| there does not seem to be any point in convoluting the early |
| out of the alias check. */ |
| /* set_mem_alias_set (x, alias_set); */ |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| /* Look for any MEMs in the current sequence of insns and set the |
| in-struct, unchanging, and volatile flags from the flags in REF. |
| If REF is not a MEM, don't do anything. */ |
| |
| void |
| mt_set_memflags (rtx ref) |
| { |
| rtx insn; |
| int in_struct_p, volatile_p; |
| |
| if (GET_CODE (ref) != MEM) |
| return; |
| |
| in_struct_p = MEM_IN_STRUCT_P (ref); |
| volatile_p = MEM_VOLATILE_P (ref); |
| |
| /* This is only called from mt.md, after having had something |
| generated from one of the insn patterns. So if everything is |
| zero, the pattern is already up-to-date. */ |
| if (! in_struct_p && ! volatile_p) |
| return; |
| |
| for (insn = get_insns (); insn; insn = NEXT_INSN (insn)) |
| mt_set_memflags_1 (insn, in_struct_p, volatile_p); |
| } |
| |
| /* Implement SECONDARY_RELOAD_CLASS. */ |
| enum reg_class |
| mt_secondary_reload_class (enum reg_class class ATTRIBUTE_UNUSED, |
| enum machine_mode mode, |
| rtx x) |
| { |
| if ((mode == QImode && (!TARGET_BYTE_ACCESS)) || mode == HImode) |
| { |
| if (GET_CODE (x) == MEM |
| || (GET_CODE (x) == REG && true_regnum (x) == -1) |
| || (GET_CODE (x) == SUBREG |
| && (GET_CODE (SUBREG_REG (x)) == MEM |
| || (GET_CODE (SUBREG_REG (x)) == REG |
| && true_regnum (SUBREG_REG (x)) == -1)))) |
| return GENERAL_REGS; |
| } |
| |
| return NO_REGS; |
| } |
| |
| /* Handle FUNCTION_VALUE, FUNCTION_OUTGOING_VALUE, and LIBCALL_VALUE |
| macros. */ |
| rtx |
| mt_function_value (tree valtype, enum machine_mode mode, tree func_decl ATTRIBUTE_UNUSED) |
| { |
| if ((mode) == DImode || (mode) == DFmode) |
| return gen_rtx_MEM (mode, gen_rtx_REG (mode, RETURN_VALUE_REGNUM)); |
| |
| if (valtype) |
| mode = TYPE_MODE (valtype); |
| |
| return gen_rtx_REG (mode, RETURN_VALUE_REGNUM); |
| } |
| |
| /* Split a move into two smaller pieces. |
| MODE indicates the reduced mode. OPERANDS[0] is the original destination |
| OPERANDS[1] is the original src. The new destinations are |
| OPERANDS[2] and OPERANDS[4], while the new sources are OPERANDS[3] |
| and OPERANDS[5]. */ |
| |
| void |
| mt_split_words (enum machine_mode nmode, |
| enum machine_mode omode, |
| rtx *operands) |
| { |
| rtx dl,dh; /* src/dest pieces. */ |
| rtx sl,sh; |
| int move_high_first = 0; /* Assume no overlap. */ |
| |
| switch (GET_CODE (operands[0])) /* Dest. */ |
| { |
| case SUBREG: |
| case REG: |
| if ((GET_CODE (operands[1]) == REG |
| || GET_CODE (operands[1]) == SUBREG) |
| && true_regnum (operands[0]) <= true_regnum (operands[1])) |
| move_high_first = 1; |
| |
| if (GET_CODE (operands[0]) == SUBREG) |
| { |
| dl = gen_rtx_SUBREG (nmode, SUBREG_REG (operands[0]), |
| SUBREG_BYTE (operands[0]) + GET_MODE_SIZE (nmode)); |
| dh = gen_rtx_SUBREG (nmode, SUBREG_REG (operands[0]), SUBREG_BYTE (operands[0])); |
| } |
| else if (GET_CODE (operands[0]) == REG && ! IS_PSEUDO_P (operands[0])) |
| { |
| int r = REGNO (operands[0]); |
| dh = gen_rtx_REG (nmode, r); |
| dl = gen_rtx_REG (nmode, r + HARD_REGNO_NREGS (r, nmode)); |
| } |
| else |
| { |
| dh = gen_rtx_SUBREG (nmode, operands[0], 0); |
| dl = gen_rtx_SUBREG (nmode, operands[0], GET_MODE_SIZE (nmode)); |
| } |
| break; |
| |
| case MEM: |
| switch (GET_CODE (XEXP (operands[0], 0))) |
| { |
| case POST_INC: |
| case POST_DEC: |
| gcc_unreachable (); |
| default: |
| dl = operand_subword (operands[0], |
| GET_MODE_SIZE (nmode)/UNITS_PER_WORD, |
| 0, omode); |
| dh = operand_subword (operands[0], 0, 0, omode); |
| } |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| |
| switch (GET_CODE (operands[1])) |
| { |
| case REG: |
| if (! IS_PSEUDO_P (operands[1])) |
| { |
| int r = REGNO (operands[1]); |
| |
| sh = gen_rtx_REG (nmode, r); |
| sl = gen_rtx_REG (nmode, r + HARD_REGNO_NREGS (r, nmode)); |
| } |
| else |
| { |
| sh = gen_rtx_SUBREG (nmode, operands[1], 0); |
| sl = gen_rtx_SUBREG (nmode, operands[1], GET_MODE_SIZE (nmode)); |
| } |
| break; |
| |
| case CONST_DOUBLE: |
| if (operands[1] == const0_rtx) |
| sh = sl = const0_rtx; |
| else |
| split_double (operands[1], & sh, & sl); |
| break; |
| |
| case CONST_INT: |
| if (operands[1] == const0_rtx) |
| sh = sl = const0_rtx; |
| else |
| { |
| int vl, vh; |
| |
| switch (nmode) |
| { |
| default: |
| gcc_unreachable (); |
| } |
| |
| sl = GEN_INT (vl); |
| sh = GEN_INT (vh); |
| } |
| break; |
| |
| case SUBREG: |
| sl = gen_rtx_SUBREG (nmode, |
| SUBREG_REG (operands[1]), |
| SUBREG_BYTE (operands[1]) + GET_MODE_SIZE (nmode)); |
| sh = gen_rtx_SUBREG (nmode, |
| SUBREG_REG (operands[1]), |
| SUBREG_BYTE (operands[1])); |
| break; |
| |
| case MEM: |
| switch (GET_CODE (XEXP (operands[1], 0))) |
| { |
| case POST_DEC: |
| case POST_INC: |
| gcc_unreachable (); |
| break; |
| default: |
| sl = operand_subword (operands[1], |
| GET_MODE_SIZE (nmode)/UNITS_PER_WORD, |
| 0, omode); |
| sh = operand_subword (operands[1], 0, 0, omode); |
| |
| /* Check if the DF load is going to clobber the register |
| used for the address, and if so make sure that is going |
| to be the second move. */ |
| if (GET_CODE (dl) == REG |
| && true_regnum (dl) |
| == true_regnum (XEXP (XEXP (sl, 0 ), 0))) |
| move_high_first = 1; |
| } |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| |
| if (move_high_first) |
| { |
| operands[2] = dh; |
| operands[3] = sh; |
| operands[4] = dl; |
| operands[5] = sl; |
| } |
| else |
| { |
| operands[2] = dl; |
| operands[3] = sl; |
| operands[4] = dh; |
| operands[5] = sh; |
| } |
| return; |
| } |
| |
| /* Implement TARGET_MUST_PASS_IN_STACK hook. */ |
| static bool |
| mt_pass_in_stack (enum machine_mode mode ATTRIBUTE_UNUSED, tree type) |
| { |
| return (((type) != 0 |
| && (TREE_CODE (TYPE_SIZE (type)) != INTEGER_CST |
| || TREE_ADDRESSABLE (type)))); |
| } |
| |
| /* Increment the counter for the number of loop instructions in the |
| current function. */ |
| |
| void mt_add_loop (void) |
| { |
| cfun->machine->has_loops++; |
| } |
| |
| |
| /* Maximum loop nesting depth. */ |
| #define MAX_LOOP_DEPTH 4 |
| /* Maximum size of a loop (allows some headroom for delayed branch slot |
| filling. */ |
| #define MAX_LOOP_LENGTH (200 * 4) |
| |
| /* We need to keep a vector of loops */ |
| typedef struct loop_info *loop_info; |
| DEF_VEC_P (loop_info); |
| DEF_VEC_ALLOC_P (loop_info,heap); |
| |
| /* Information about a loop we have found (or are in the process of |
| finding). */ |
| struct loop_info GTY (()) |
| { |
| /* loop number, for dumps */ |
| int loop_no; |
| |
| /* Predecessor block of the loop. This is the one that falls into |
| the loop and contains the initialization instruction. */ |
| basic_block predecessor; |
| |
| /* First block in the loop. This is the one branched to by the dbnz |
| insn. */ |
| basic_block head; |
| |
| /* Last block in the loop (the one with the dbnz insn */ |
| basic_block tail; |
| |
| /* The successor block of the loop. This is the one the dbnz insn |
| falls into. */ |
| basic_block successor; |
| |
| /* The dbnz insn. */ |
| rtx dbnz; |
| |
| /* The initialization insn. */ |
| rtx init; |
| |
| /* The new initialization instruction. */ |
| rtx loop_init; |
| |
| /* The new ending instruction. */ |
| rtx loop_end; |
| |
| /* The new label placed at the end of the loop. */ |
| rtx end_label; |
| |
| /* The nesting depth of the loop. Set to -1 for a bad loop. */ |
| int depth; |
| |
| /* The length of the loop. */ |
| int length; |
| |
| /* Next loop in the graph. */ |
| struct loop_info *next; |
| |
| /* Vector of blocks only within the loop, (excluding those within |
| inner loops). */ |
| VEC (basic_block,heap) *blocks; |
| |
| /* Vector of inner loops within this loop */ |
| VEC (loop_info,heap) *loops; |
| }; |
| |
| /* Information used during loop detection. */ |
| typedef struct loop_work GTY(()) |
| { |
| /* Basic block to be scanned. */ |
| basic_block block; |
| |
| /* Loop it will be within. */ |
| loop_info loop; |
| } loop_work; |
| |
| /* Work list. */ |
| DEF_VEC_O (loop_work); |
| DEF_VEC_ALLOC_O (loop_work,heap); |
| |
| /* Determine the nesting and length of LOOP. Return false if the loop |
| is bad. */ |
| |
| static bool |
| mt_loop_nesting (loop_info loop) |
| { |
| loop_info inner; |
| unsigned ix; |
| int inner_depth = 0; |
| |
| if (!loop->depth) |
| { |
| /* Make sure we only have one entry point. */ |
| if (EDGE_COUNT (loop->head->preds) == 2) |
| { |
| loop->predecessor = EDGE_PRED (loop->head, 0)->src; |
| if (loop->predecessor == loop->tail) |
| /* We wanted the other predecessor. */ |
| loop->predecessor = EDGE_PRED (loop->head, 1)->src; |
| |
| /* We can only place a loop insn on a fall through edge of a |
| single exit block. */ |
| if (EDGE_COUNT (loop->predecessor->succs) != 1 |
| || !(EDGE_SUCC (loop->predecessor, 0)->flags & EDGE_FALLTHRU)) |
| loop->predecessor = NULL; |
| } |
| |
| /* Mark this loop as bad for now. */ |
| loop->depth = -1; |
| if (loop->predecessor) |
| { |
| for (ix = 0; VEC_iterate (loop_info, loop->loops, ix++, inner);) |
| { |
| if (!inner->depth) |
| mt_loop_nesting (inner); |
| |
| if (inner->depth < 0) |
| { |
| inner_depth = -1; |
| break; |
| } |
| |
| if (inner_depth < inner->depth) |
| inner_depth = inner->depth; |
| loop->length += inner->length; |
| } |
| |
| /* Set the proper loop depth, if it was good. */ |
| if (inner_depth >= 0) |
| loop->depth = inner_depth + 1; |
| } |
| } |
| return (loop->depth > 0 |
| && loop->predecessor |
| && loop->depth < MAX_LOOP_DEPTH |
| && loop->length < MAX_LOOP_LENGTH); |
| } |
| |
| /* Determine the length of block BB. */ |
| |
| static int |
| mt_block_length (basic_block bb) |
| { |
| int length = 0; |
| rtx insn; |
| |
| for (insn = BB_HEAD (bb); |
| insn != NEXT_INSN (BB_END (bb)); |
| insn = NEXT_INSN (insn)) |
| { |
| if (!INSN_P (insn)) |
| continue; |
| if (CALL_P (insn)) |
| { |
| /* Calls are not allowed in loops. */ |
| length = MAX_LOOP_LENGTH + 1; |
| break; |
| } |
| |
| length += get_attr_length (insn); |
| } |
| return length; |
| } |
| |
| /* Scan the blocks of LOOP (and its inferiors) looking for uses of |
| REG. Return true, if we find any. Don't count the loop's dbnz |
| insn if it matches DBNZ. */ |
| |
| static bool |
| mt_scan_loop (loop_info loop, rtx reg, rtx dbnz) |
| { |
| unsigned ix; |
| loop_info inner; |
| basic_block bb; |
| |
| for (ix = 0; VEC_iterate (basic_block, loop->blocks, ix, bb); ix++) |
| { |
| rtx insn; |
| |
| for (insn = BB_HEAD (bb); |
| insn != NEXT_INSN (BB_END (bb)); |
| insn = NEXT_INSN (insn)) |
| { |
| if (!INSN_P (insn)) |
| continue; |
| if (insn == dbnz) |
| continue; |
| if (reg_mentioned_p (reg, PATTERN (insn))) |
| return true; |
| } |
| } |
| for (ix = 0; VEC_iterate (loop_info, loop->loops, ix, inner); ix++) |
| if (mt_scan_loop (inner, reg, NULL_RTX)) |
| return true; |
| |
| return false; |
| } |
| |
| /* MS2 has a loop instruction which needs to be placed just before the |
| loop. It indicates the end of the loop and specifies the number of |
| loop iterations. It can be nested with an automatically maintained |
| stack of counter and end address registers. It's an ideal |
| candidate for doloop. Unfortunately, gcc presumes that loops |
| always end with an explicit instruction, and the doloop_begin |
| instruction is not a flow control instruction so it can be |
| scheduled earlier than just before the start of the loop. To make |
| matters worse, the optimization pipeline can duplicate loop exit |
| and entrance blocks and fails to track abnormally exiting loops. |
| Thus we cannot simply use doloop. |
| |
| What we do is emit a dbnz pattern for the doloop optimization, and |
| let that be optimized as normal. Then in machine dependent reorg |
| we have to repeat the loop searching algorithm. We use the |
| flow graph to find closed loops ending in a dbnz insn. We then try |
| and convert it to use the loop instruction. The conditions are, |
| |
| * the loop has no abnormal exits, duplicated end conditions or |
| duplicated entrance blocks |
| |
| * the loop counter register is only used in the dbnz instruction |
| within the loop |
| |
| * we can find the instruction setting the initial value of the loop |
| counter |
| |
| * the loop is not executed more than 65535 times. (This might be |
| changed to 2^32-1, and would therefore allow variable initializers.) |
| |
| * the loop is not nested more than 4 deep 5) there are no |
| subroutine calls in the loop. */ |
| |
| static void |
| mt_reorg_loops (FILE *dump_file) |
| { |
| basic_block bb; |
| loop_info loops = NULL; |
| loop_info loop; |
| int nloops = 0; |
| unsigned dwork = 0; |
| VEC (loop_work,heap) *works = VEC_alloc (loop_work,heap,20); |
| loop_work *work; |
| edge e; |
| edge_iterator ei; |
| bool replaced = false; |
| |
| /* Find all the possible loop tails. This means searching for every |
| dbnz instruction. For each one found, create a loop_info |
| structure and add the head block to the work list. */ |
| FOR_EACH_BB (bb) |
| { |
| rtx tail = BB_END (bb); |
| |
| while (GET_CODE (tail) == NOTE) |
| tail = PREV_INSN (tail); |
| |
| bb->aux = NULL; |
| if (recog_memoized (tail) == CODE_FOR_decrement_and_branch_until_zero) |
| { |
| /* A possible loop end */ |
| |
| loop = XNEW (struct loop_info); |
| loop->next = loops; |
| loops = loop; |
| loop->tail = bb; |
| loop->head = BRANCH_EDGE (bb)->dest; |
| loop->successor = FALLTHRU_EDGE (bb)->dest; |
| loop->predecessor = NULL; |
| loop->dbnz = tail; |
| loop->depth = 0; |
| loop->length = mt_block_length (bb); |
| loop->blocks = VEC_alloc (basic_block, heap, 20); |
| VEC_quick_push (basic_block, loop->blocks, bb); |
| loop->loops = NULL; |
| loop->loop_no = nloops++; |
| |
| loop->init = loop->end_label = NULL_RTX; |
| loop->loop_init = loop->loop_end = NULL_RTX; |
| |
| work = VEC_safe_push (loop_work, heap, works, NULL); |
| work->block = loop->head; |
| work->loop = loop; |
| |
| bb->aux = loop; |
| |
| if (dump_file) |
| { |
| fprintf (dump_file, ";; potential loop %d ending at\n", |
| loop->loop_no); |
| print_rtl_single (dump_file, tail); |
| } |
| } |
| } |
| |
| /* Now find all the closed loops. |
| until work list empty, |
| if block's auxptr is set |
| if != loop slot |
| if block's loop's start != block |
| mark loop as bad |
| else |
| append block's loop's fallthrough block to worklist |
| increment this loop's depth |
| else if block is exit block |
| mark loop as bad |
| else |
| set auxptr |
| for each target of block |
| add to worklist */ |
| while (VEC_iterate (loop_work, works, dwork++, work)) |
| { |
| loop = work->loop; |
| bb = work->block; |
| if (bb == EXIT_BLOCK_PTR) |
| /* We've reached the exit block. The loop must be bad. */ |
| loop->depth = -1; |
| else if (!bb->aux) |
| { |
| /* We've not seen this block before. Add it to the loop's |
| list and then add each successor to the work list. */ |
| bb->aux = loop; |
| loop->length += mt_block_length (bb); |
| VEC_safe_push (basic_block, heap, loop->blocks, bb); |
| FOR_EACH_EDGE (e, ei, bb->succs) |
| { |
| if (!VEC_space (loop_work, works, 1)) |
| { |
| if (dwork) |
| { |
| VEC_block_remove (loop_work, works, 0, dwork); |
| dwork = 0; |
| } |
| else |
| VEC_reserve (loop_work, heap, works, 1); |
| } |
| work = VEC_quick_push (loop_work, works, NULL); |
| work->block = EDGE_SUCC (bb, ei.index)->dest; |
| work->loop = loop; |
| } |
| } |
| else if (bb->aux != loop) |
| { |
| /* We've seen this block in a different loop. If it's not |
| the other loop's head, then this loop must be bad. |
| Otherwise, the other loop might be a nested loop, so |
| continue from that loop's successor. */ |
| loop_info other = bb->aux; |
| |
| if (other->head != bb) |
| loop->depth = -1; |
| else |
| { |
| VEC_safe_push (loop_info, heap, loop->loops, other); |
| work = VEC_safe_push (loop_work, heap, works, NULL); |
| work->loop = loop; |
| work->block = other->successor; |
| } |
| } |
| } |
| VEC_free (loop_work, heap, works); |
| |
| /* Now optimize the loops. */ |
| for (loop = loops; loop; loop = loop->next) |
| { |
| rtx iter_reg, insn, init_insn; |
| rtx init_val, loop_end, loop_init, end_label, head_label; |
| |
| if (!mt_loop_nesting (loop)) |
| { |
| if (dump_file) |
| fprintf (dump_file, ";; loop %d is bad\n", loop->loop_no); |
| continue; |
| } |
| |
| /* Get the loop iteration register. */ |
| iter_reg = SET_DEST (XVECEXP (PATTERN (loop->dbnz), 0, 1)); |
| |
| if (!REG_P (iter_reg)) |
| { |
| /* Spilled */ |
| if (dump_file) |
| fprintf (dump_file, ";; loop %d has spilled iteration count\n", |
| loop->loop_no); |
| continue; |
| } |
| |
| /* Look for the initializing insn */ |
| init_insn = NULL_RTX; |
| for (insn = BB_END (loop->predecessor); |
| insn != PREV_INSN (BB_HEAD (loop->predecessor)); |
| insn = PREV_INSN (insn)) |
| { |
| if (!INSN_P (insn)) |
| continue; |
| if (reg_mentioned_p (iter_reg, PATTERN (insn))) |
| { |
| rtx set = single_set (insn); |
| |
| if (set && rtx_equal_p (iter_reg, SET_DEST (set))) |
| init_insn = insn; |
| break; |
| } |
| } |
| |
| if (!init_insn) |
| { |
| if (dump_file) |
| fprintf (dump_file, ";; loop %d has no initializer\n", |
| loop->loop_no); |
| continue; |
| } |
| if (dump_file) |
| { |
| fprintf (dump_file, ";; loop %d initialized by\n", |
| loop->loop_no); |
| print_rtl_single (dump_file, init_insn); |
| } |
| |
| init_val = PATTERN (init_insn); |
| if (GET_CODE (init_val) == SET) |
| init_val = SET_SRC (init_val); |
| if (GET_CODE (init_val) != CONST_INT || INTVAL (init_val) >= 65535) |
| { |
| if (dump_file) |
| fprintf (dump_file, ";; loop %d has complex initializer\n", |
| loop->loop_no); |
| continue; |
| } |
| |
| /* Scan all the blocks to make sure they don't use iter_reg. */ |
| if (mt_scan_loop (loop, iter_reg, loop->dbnz)) |
| { |
| if (dump_file) |
| fprintf (dump_file, ";; loop %d uses iterator\n", |
| loop->loop_no); |
| continue; |
| } |
| |
| /* The loop is good for replacement. */ |
| |
| /* loop is 1 based, dbnz is zero based. */ |
| init_val = GEN_INT (INTVAL (init_val) + 1); |
| |
| iter_reg = gen_rtx_REG (SImode, LOOP_FIRST + loop->depth - 1); |
| end_label = gen_label_rtx (); |
| head_label = XEXP (SET_SRC (XVECEXP (PATTERN (loop->dbnz), 0, 0)), 1); |
| loop_end = gen_loop_end (iter_reg, head_label); |
| loop_init = gen_loop_init (iter_reg, init_val, end_label); |
| loop->init = init_insn; |
| loop->end_label = end_label; |
| loop->loop_init = loop_init; |
| loop->loop_end = loop_end; |
| replaced = true; |
| |
| if (dump_file) |
| { |
| fprintf (dump_file, ";; replacing loop %d initializer with\n", |
| loop->loop_no); |
| print_rtl_single (dump_file, loop->loop_init); |
| fprintf (dump_file, ";; replacing loop %d terminator with\n", |
| loop->loop_no); |
| print_rtl_single (dump_file, loop->loop_end); |
| } |
| } |
| |
| /* Now apply the optimizations. Do it this way so we don't mess up |
| the flow graph half way through. */ |
| for (loop = loops; loop; loop = loop->next) |
| if (loop->loop_init) |
| { |
| emit_jump_insn_after (loop->loop_init, BB_END (loop->predecessor)); |
| delete_insn (loop->init); |
| emit_label_before (loop->end_label, loop->dbnz); |
| emit_jump_insn_before (loop->loop_end, loop->dbnz); |
| delete_insn (loop->dbnz); |
| } |
| |
| /* Free up the loop structures */ |
| while (loops) |
| { |
| loop = loops; |
| loops = loop->next; |
| VEC_free (loop_info, heap, loop->loops); |
| VEC_free (basic_block, heap, loop->blocks); |
| XDELETE (loop); |
| } |
| |
| if (replaced && dump_file) |
| { |
| fprintf (dump_file, ";; Replaced loops\n"); |
| print_rtl (dump_file, get_insns ()); |
| } |
| } |
| |
| /* Structures to hold branch information during reorg. */ |
| typedef struct branch_info |
| { |
| rtx insn; /* The branch insn. */ |
| |
| struct branch_info *next; |
| } branch_info; |
| |
| typedef struct label_info |
| { |
| rtx label; /* The label. */ |
| branch_info *branches; /* branches to this label. */ |
| struct label_info *next; |
| } label_info; |
| |
| /* Chain of labels found in current function, used during reorg. */ |
| static label_info *mt_labels; |
| |
| /* If *X is a label, add INSN to the list of branches for that |
| label. */ |
| |
| static int |
| mt_add_branches (rtx *x, void *insn) |
| { |
| if (GET_CODE (*x) == LABEL_REF) |
| { |
| branch_info *branch = xmalloc (sizeof (*branch)); |
| rtx label = XEXP (*x, 0); |
| label_info *info; |
| |
| for (info = mt_labels; info; info = info->next) |
| if (info->label == label) |
| break; |
| |
| if (!info) |
| { |
| info = xmalloc (sizeof (*info)); |
| info->next = mt_labels; |
| mt_labels = info; |
| |
| info->label = label; |
| info->branches = NULL; |
| } |
| |
| branch->next = info->branches; |
| info->branches = branch; |
| branch->insn = insn; |
| } |
| return 0; |
| } |
| |
| /* If BRANCH has a filled delay slot, check if INSN is dependent upon |
| it. If so, undo the delay slot fill. Returns the next insn, if |
| we patch out the branch. Returns the branch insn, if we cannot |
| patch out the branch (due to anti-dependency in the delay slot). |
| In that case, the caller must insert nops at the branch target. */ |
| |
| static rtx |
| mt_check_delay_slot (rtx branch, rtx insn) |
| { |
| rtx slot; |
| rtx tmp; |
| rtx p; |
| rtx jmp; |
| |
| gcc_assert (GET_CODE (PATTERN (branch)) == SEQUENCE); |
| if (INSN_DELETED_P (branch)) |
| return NULL_RTX; |
| slot = XVECEXP (PATTERN (branch), 0, 1); |
| |
| tmp = PATTERN (insn); |
| note_stores (PATTERN (slot), insn_dependent_p_1, &tmp); |
| if (tmp) |
| /* Not dependent. */ |
| return NULL_RTX; |
| |
| /* Undo the delay slot. */ |
| jmp = XVECEXP (PATTERN (branch), 0, 0); |
| |
| tmp = PATTERN (jmp); |
| note_stores (PATTERN (slot), insn_dependent_p_1, &tmp); |
| if (!tmp) |
| /* Anti dependent. */ |
| return branch; |
| |
| p = PREV_INSN (branch); |
| NEXT_INSN (p) = slot; |
| PREV_INSN (slot) = p; |
| NEXT_INSN (slot) = jmp; |
| PREV_INSN (jmp) = slot; |
| NEXT_INSN (jmp) = branch; |
| PREV_INSN (branch) = jmp; |
| XVECEXP (PATTERN (branch), 0, 0) = NULL_RTX; |
| XVECEXP (PATTERN (branch), 0, 1) = NULL_RTX; |
| delete_insn (branch); |
| return jmp; |
| } |
| |
| /* Insert nops to satisfy pipeline constraints. We only deal with ms2 |
| constraints here. Earlier CPUs are dealt with by inserting nops with |
| final_prescan (but that can lead to inferior code, and is |
| impractical with ms2's JAL hazard). |
| |
| ms2 dynamic constraints |
| 1) a load and a following use must be separated by one insn |
| 2) an insn and a following dependent call must be separated by two insns |
| |
| only arith insns are placed in delay slots so #1 cannot happen with |
| a load in a delay slot. #2 can happen with an arith insn in the |
| delay slot. */ |
| |
| static void |
| mt_reorg_hazard (void) |
| { |
| rtx insn, next; |
| |
| /* Find all the branches */ |
| for (insn = get_insns (); |
| insn; |
| insn = NEXT_INSN (insn)) |
| { |
| rtx jmp; |
| |
| if (!INSN_P (insn)) |
| continue; |
| |
| jmp = PATTERN (insn); |
| |
| if (GET_CODE (jmp) != SEQUENCE) |
| /* If it's not got a filled delay slot, then it can't |
| conflict. */ |
| continue; |
| |
| jmp = XVECEXP (jmp, 0, 0); |
| |
| if (recog_memoized (jmp) == CODE_FOR_tablejump) |
| for (jmp = XEXP (XEXP (XVECEXP (PATTERN (jmp), 0, 1), 0), 0); |
| !JUMP_TABLE_DATA_P (jmp); |
| jmp = NEXT_INSN (jmp)) |
| continue; |
| |
| for_each_rtx (&PATTERN (jmp), mt_add_branches, insn); |
| } |
| |
| /* Now scan for dependencies. */ |
| for (insn = get_insns (); |
| insn && !INSN_P (insn); |
| insn = NEXT_INSN (insn)) |
| continue; |
| |
| for (; |
| insn; |
| insn = next) |
| { |
| rtx jmp, tmp; |
| enum attr_type attr; |
| |
| gcc_assert (INSN_P (insn) && !INSN_DELETED_P (insn)); |
| for (next = NEXT_INSN (insn); |
| next; |
| next = NEXT_INSN (next)) |
| { |
| if (!INSN_P (next)) |
| continue; |
| if (GET_CODE (PATTERN (next)) != USE) |
| break; |
| } |
| |
| jmp = insn; |
| if (GET_CODE (PATTERN (insn)) == SEQUENCE) |
| jmp = XVECEXP (PATTERN (insn), 0, 0); |
| |
| attr = recog_memoized (jmp) >= 0 ? get_attr_type (jmp) : TYPE_UNKNOWN; |
| |
| if (next && attr == TYPE_LOAD) |
| { |
| /* A load. See if NEXT is dependent, and if so insert a |
| nop. */ |
| |
| tmp = PATTERN (next); |
| if (GET_CODE (tmp) == SEQUENCE) |
| tmp = PATTERN (XVECEXP (tmp, 0, 0)); |
| note_stores (PATTERN (insn), insn_dependent_p_1, &tmp); |
| if (!tmp) |
| emit_insn_after (gen_nop (), insn); |
| } |
| |
| if (attr == TYPE_CALL) |
| { |
| /* A call. Make sure we're not dependent on either of the |
| previous two dynamic instructions. */ |
| int nops = 0; |
| int count; |
| rtx prev = insn; |
| rtx rescan = NULL_RTX; |
| |
| for (count = 2; count && !nops;) |
| { |
| int type; |
| |
| prev = PREV_INSN (prev); |
| if (!prev) |
| { |
| /* If we reach the start of the function, we must |
| presume the caller set the address in the delay |
| slot of the call instruction. */ |
| nops = count; |
| break; |
| } |
| |
| if (BARRIER_P (prev)) |
| break; |
| if (LABEL_P (prev)) |
| { |
| /* Look at branches to this label. */ |
| label_info *label; |
| branch_info *branch; |
| |
| for (label = mt_labels; |
| label; |
| label = label->next) |
| if (label->label == prev) |
| { |
| for (branch = label->branches; |
| branch; |
| branch = branch->next) |
| { |
| tmp = mt_check_delay_slot (branch->insn, jmp); |
| |
| if (tmp == branch->insn) |
| { |
| nops = count; |
| break; |
| } |
| |
| if (tmp && branch->insn == next) |
| rescan = tmp; |
| } |
| break; |
| } |
| continue; |
| } |
| if (!INSN_P (prev) || GET_CODE (PATTERN (prev)) == USE) |
| continue; |
| |
| if (GET_CODE (PATTERN (prev)) == SEQUENCE) |
| { |
| /* Look at the delay slot. */ |
| tmp = mt_check_delay_slot (prev, jmp); |
| if (tmp == prev) |
| nops = count; |
| break; |
| } |
| |
| type = (INSN_CODE (prev) >= 0 ? get_attr_type (prev) |
| : TYPE_COMPLEX); |
| if (type == TYPE_CALL || type == TYPE_BRANCH) |
| break; |
| |
| if (type == TYPE_LOAD |
| || type == TYPE_ARITH |
| || type == TYPE_COMPLEX) |
| { |
| tmp = PATTERN (jmp); |
| note_stores (PATTERN (prev), insn_dependent_p_1, &tmp); |
| if (!tmp) |
| { |
| nops = count; |
| break; |
| } |
| } |
| |
| if (INSN_CODE (prev) >= 0) |
| count--; |
| } |
| |
| if (rescan) |
| for (next = NEXT_INSN (rescan); |
| next && !INSN_P (next); |
| next = NEXT_INSN (next)) |
| continue; |
| while (nops--) |
| emit_insn_before (gen_nop (), insn); |
| } |
| } |
| |
| /* Free the data structures. */ |
| while (mt_labels) |
| { |
| label_info *label = mt_labels; |
| branch_info *branch, *next; |
| |
| mt_labels = label->next; |
| for (branch = label->branches; branch; branch = next) |
| { |
| next = branch->next; |
| free (branch); |
| } |
| free (label); |
| } |
| } |
| |
| /* Fixup the looping instructions, do delayed branch scheduling, fixup |
| scheduling hazards. */ |
| |
| static void |
| mt_machine_reorg (void) |
| { |
| if (cfun->machine->has_loops && TARGET_MS2) |
| mt_reorg_loops (dump_file); |
| |
| if (mt_flag_delayed_branch) |
| dbr_schedule (get_insns ()); |
| |
| if (TARGET_MS2) |
| { |
| /* Force all instructions to be split into their final form. */ |
| split_all_insns_noflow (); |
| mt_reorg_hazard (); |
| } |
| } |
| |
| /* Initialize the GCC target structure. */ |
| const struct attribute_spec mt_attribute_table[]; |
| |
| #undef TARGET_ATTRIBUTE_TABLE |
| #define TARGET_ATTRIBUTE_TABLE mt_attribute_table |
| #undef TARGET_STRUCT_VALUE_RTX |
| #define TARGET_STRUCT_VALUE_RTX mt_struct_value_rtx |
| #undef TARGET_PROMOTE_PROTOTYPES |
| #define TARGET_PROMOTE_PROTOTYPES hook_bool_tree_true |
| #undef TARGET_PASS_BY_REFERENCE |
| #define TARGET_PASS_BY_REFERENCE mt_pass_by_reference |
| #undef TARGET_MUST_PASS_IN_STACK |
| #define TARGET_MUST_PASS_IN_STACK mt_pass_in_stack |
| #undef TARGET_ARG_PARTIAL_BYTES |
| #define TARGET_ARG_PARTIAL_BYTES mt_arg_partial_bytes |
| #undef TARGET_SETUP_INCOMING_VARARGS |
| #define TARGET_SETUP_INCOMING_VARARGS mt_setup_incoming_varargs |
| #undef TARGET_MACHINE_DEPENDENT_REORG |
| #define TARGET_MACHINE_DEPENDENT_REORG mt_machine_reorg |
| |
| struct gcc_target targetm = TARGET_INITIALIZER; |
| |
| #include "gt-mt.h" |