diff options
author | Nathan Sidwell <nathan@gcc.gnu.org> | 2005-12-12 10:50:25 +0000 |
---|---|---|
committer | Nathan Sidwell <nathan@gcc.gnu.org> | 2005-12-12 10:50:25 +0000 |
commit | dcc8a60324e08cdd4b9524c9326eebf28be79ca7 (patch) | |
tree | bfb4ac425969e27302e5963c2c275e96e907ee15 /gcc/config/mt/mt.c | |
parent | 9d26d2641260ab032a36feae4c1dd31874bbc90e (diff) | |
download | gcc-dcc8a60324e08cdd4b9524c9326eebf28be79ca7.zip gcc-dcc8a60324e08cdd4b9524c9326eebf28be79ca7.tar.gz gcc-dcc8a60324e08cdd4b9524c9326eebf28be79ca7.tar.bz2 |
renam ms1 files to mt (part 2)
From-SVN: r108402
Diffstat (limited to 'gcc/config/mt/mt.c')
-rw-r--r-- | gcc/config/mt/mt.c | 2498 |
1 files changed, 2498 insertions, 0 deletions
diff --git a/gcc/config/mt/mt.c b/gcc/config/mt/mt.c new file mode 100644 index 0000000..3695f0f --- /dev/null +++ b/gcc/config/mt/mt.c @@ -0,0 +1,2498 @@ +/* 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)) + +/* First GPR. */ +#define MS1_INT_ARG_FIRST 1 + +/* 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 * ms1_compare_op0; +struct rtx_def * ms1_compare_op1; + +/* Current frame information calculated by compute_frame_size. */ +struct ms1_frame_info current_frame_info; + +/* Zero structure to initialize current_frame_info. */ +struct ms1_frame_info zero_frame_info; + +/* ms1 doesn't have unsigned compares need a library call for this. */ +struct rtx_def * ms1_ucmpsi3_libcall; + +static int ms1_flag_delayed_branch; + + +static rtx +ms1_struct_value_rtx (tree fndecl ATTRIBUTE_UNUSED, + int incoming ATTRIBUTE_UNUSED) +{ + return gen_rtx_REG (Pmode, RETVAL_REGNUM); +} + +/* Implement RETURN_ADDR_RTX. */ +rtx +ms1_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 ms1_nops_required = 0; +static const char * ms1_nop_reasons = ""; + +/* Implement ASM_OUTPUT_OPCODE. */ +const char * +ms1_asm_output_opcode (FILE *f ATTRIBUTE_UNUSED, const char *ptr) +{ + if (ms1_nops_required) + fprintf (f, ";# need %d nops because of %s\n\t", + ms1_nops_required, ms1_nop_reasons); + + while (ms1_nops_required) + { + fprintf (f, "or r0, r0, r0\n\t"); + -- ms1_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 +ms1_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 ms1 processor. Remember that + the function is not called for asm insns. */ + +void +ms1_final_prescan_insn (rtx insn, + rtx * opvec ATTRIBUTE_UNUSED, + int noperands ATTRIBUTE_UNUSED) +{ + rtx prev_i; + enum attr_type prev_attr; + + ms1_nops_required = 0; + ms1_nop_reasons = ""; + + /* ms2 constraints are dealt with in reorg. */ + if (ms1_cpu == PROCESSOR_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 = ms1_get_attr_type (prev_i); + + /* Delayed branch slots already taken care of by delay branch scheduling. */ + if (prev_attr == TYPE_BRANCH) + return; + + switch (ms1_get_attr_type (insn)) + { + case TYPE_LOAD: + case TYPE_STORE: + /* Avoid consecutive memory operation. */ + if ((prev_attr == TYPE_LOAD || prev_attr == TYPE_STORE) + && ms1_cpu == PROCESSOR_MS1_64_001) + { + ms1_nops_required = 1; + ms1_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)) + { + ms1_nops_required = 1; + ms1_nop_reasons = "load->arith dependency delay"; + } + break; + + case TYPE_BRANCH: + if (insn_dependent_p (prev_i, insn)) + { + if (prev_attr == TYPE_ARITH + && ms1_cpu == PROCESSOR_MS1_64_001) + { + /* One cycle of delay between arith + instructions and branch dependent on arith. */ + ms1_nops_required = 1; + ms1_nop_reasons = "arith->branch dependency delay"; + } + else if (prev_attr == TYPE_LOAD) + { + /* Two cycles of delay are required + between load and dependent branch. */ + if (ms1_cpu == PROCESSOR_MS1_64_001) + ms1_nops_required = 2; + else + ms1_nops_required = 1; + ms1_nop_reasons = "load->branch dependency delay"; + } + } + break; + + default: + fatal_insn ("ms1_final_prescan_insn, invalid insn #1", insn); + break; + } +} + +/* Print debugging information for a frame. */ +static void +ms1_debug_stack (struct ms1_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 +ms1_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 +ms1_print_operand_address (FILE * file, rtx addr) +{ + if (GET_CODE (addr) == AND + && GET_CODE (XEXP (addr, 1)) == CONST_INT + && INTVAL (XEXP (addr, 1)) == -3) + ms1_print_operand_simple_address (file, XEXP (addr, 0)); + else + ms1_print_operand_simple_address (file, addr); +} + +/* Implement PRINT_OPERAND. */ +void +ms1_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\tor r0, r0, r0", 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 ("ms1_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: + ms1_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 +ms1_init_cumulative_args (CUMULATIVE_ARGS * cum, tree fntype, rtx libname, + tree fndecl ATTRIBUTE_UNUSED, int incoming) +{ + *cum = 0; + + if (TARGET_DEBUG_ARG) + { + fprintf (stderr, "\nms1_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 +ms1_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 = MS1_INT_ARG_FIRST; + int slotno = * cum; + + if (mode == VOIDmode || targetm.calls.must_pass_in_stack (mode, type)) + return -1; + + if (slotno >= MS1_NUM_ARG_REGS) + return -1; + + * pregno = regbase + slotno; + + return slotno; +} + +/* Implement FUNCTION_ARG. */ +rtx +ms1_function_arg (const CUMULATIVE_ARGS * cum, + enum machine_mode mode, + tree type, + int named, + int incoming_p) +{ + int slotno, regno; + rtx reg; + + slotno = ms1_function_arg_slotno (cum, mode, type, named, incoming_p, + & regno); + + if (slotno == -1) + reg = NULL_RTX; + else + reg = gen_rtx_REG (mode, regno); + + return reg; +} + +/* Implement FUNCTION_ARG_ADVANCE. */ +void +ms1_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 = ms1_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, + "ms1_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 +ms1_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 < MS1_NUM_ARG_REGS + && (cum + words) > MS1_NUM_ARG_REGS) + { + int bytes = (MS1_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 +ms1_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 +ms1_function_arg_boundary (enum machine_mode mode ATTRIBUTE_UNUSED, + tree type ATTRIBUTE_UNUSED) +{ + return BITS_PER_WORD; +} + +/* Implement REG_OK_FOR_BASE_P. */ +int +ms1_reg_ok_for_base_p (rtx x, int strict) +{ + if (strict) + return (((unsigned) REGNO (x)) < FIRST_PSEUDO_REGISTER); + return 1; +} + +/* Helper function of ms1_legitimate_address_p. Return true if XINSN + is a simple address, otherwise false. */ +static bool +ms1_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 && ms1_reg_ok_for_base_p (xinsn, strict)) + return true; + + if (GET_CODE (xinsn) == PLUS + && GET_CODE (XEXP (xinsn, 0)) == REG + && ms1_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 non-zero if + XINSN is a legitimate address on MS1. */ +int +ms1_legitimate_address_p (enum machine_mode mode, + rtx xinsn, + int strict) +{ + if (ms1_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 ms1_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 ms1_cpu; + +static struct machine_function * +ms1_init_machine_status (void) +{ + struct machine_function *f; + + f = ggc_alloc_cleared (sizeof (struct machine_function)); + + return f; +} + +/* Implement OVERRIDE_OPTIONS. */ +void +ms1_override_options (void) +{ + if (ms1_cpu_string != NULL) + { + if (!strcasecmp (ms1_cpu_string, "MS1-64-001")) + ms1_cpu = PROCESSOR_MS1_64_001; + else if (!strcasecmp (ms1_cpu_string, "MS1-16-002")) + ms1_cpu = PROCESSOR_MS1_16_002; + else if (!strcasecmp (ms1_cpu_string, "MS1-16-003")) + ms1_cpu = PROCESSOR_MS1_16_003; + else if (!strcasecmp (ms1_cpu_string, "MS2")) + ms1_cpu = PROCESSOR_MS2; + else + error ("bad value (%s) for -march= switch", ms1_cpu_string); + } + else + ms1_cpu = PROCESSOR_MS2; + + if (flag_exceptions) + { + flag_omit_frame_pointer = 0; + flag_gcse = 0; + } + + /* We do delayed branch filling in machine dependent reorg */ + ms1_flag_delayed_branch = flag_delayed_branch; + flag_delayed_branch = 0; + + init_machine_status = ms1_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 rtx +ms1_builtin_saveregs (void) +{ + int first_reg = 0; + rtx address; + int regno; + + for (regno = first_reg; regno < MS1_NUM_ARG_REGS; regno ++) + emit_move_insn (gen_rtx_MEM (word_mode, + gen_rtx_PLUS (Pmode, + gen_rtx_REG (SImode, ARG_POINTER_REGNUM), + GEN_INT (UNITS_PER_WORD * regno))), + gen_rtx_REG (word_mode, + MS1_INT_ARG_FIRST + regno)); + + address = gen_rtx_PLUS (Pmode, + gen_rtx_REG (SImode, ARG_POINTER_REGNUM), + GEN_INT (UNITS_PER_WORD * first_reg)); + return address; +} + +/* Implement `va_start'. */ + +void +ms1_va_start (tree valist, rtx nextarg) +{ + ms1_builtin_saveregs (); + std_expand_builtin_va_start (valist, nextarg); +} + +/* 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 +ms1_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 +ms1_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 +ms1_emit_save_fp (enum save_direction direction, + struct ms1_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; + ms1_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 +ms1_emit_save_regs (enum save_direction direction, + struct ms1_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 + ms1_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; + ms1_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; + ms1_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 +ms1_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 +ms1_expand_prologue (void) +{ + rtx size_rtx, insn; + unsigned int frame_size; + + if (ms1_interrupt_function_p (current_function_decl)) + { + interrupt_handler = 1; + if (cfun->machine) + cfun->machine->interrupt_handler = 1; + } + + ms1_compute_frame_size (get_frame_size ()); + + if (TARGET_DEBUG_STACK) + ms1_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. */ + ms1_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. */ + ms1_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 +ms1_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 +ms1_expand_epilogue (enum epilogue_type eh_mode) +{ + rtx size_rtx, insn; + unsigned frame_size; + + ms1_compute_frame_size (get_frame_size ()); + + if (TARGET_DEBUG_STACK) + ms1_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. */ + ms1_emit_save_fp (FROM_MEM_TO_PROCESSOR, current_frame_info); + ms1_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 +ms1_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 +ms1_emit_eh_epilogue (rtx * operands ATTRIBUTE_UNUSED) +{ + cfun->machine->eh_stack_adjust = EH_RETURN_STACKADJ_RTX; /* operands[0]; */ + ms1_expand_epilogue (EH_EPILOGUE); +} + +/* Handle an "interrupt" attribute. */ +static tree +ms1_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 ms1_attribute_table[] = +{ + /* name, min, max, decl?, type?, func?, handler */ + { "interrupt", 0, 0, false, false, false, ms1_handle_interrupt_attribute }, + { NULL, 0, 0, false, false, false, NULL } +}; + +/* Implement INITIAL_ELIMINATION_OFFSET. */ +int +ms1_initial_elimination_offset (int from, int to) +{ + ms1_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 +ms1_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(MS1_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 +ms1_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 = ms1_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 +ms1_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--) + ms1_set_memflags_1 (XVECEXP (x, 0, i), in_struct_p, volatile_p); + break; + + case INSN: + ms1_set_memflags_1 (PATTERN (x), in_struct_p, volatile_p); + break; + + case SET: + ms1_set_memflags_1 (SET_DEST (x), in_struct_p, volatile_p); + ms1_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 +ms1_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 ms1.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)) + ms1_set_memflags_1 (insn, in_struct_p, volatile_p); +} + +/* Implement SECONDARY_RELOAD_CLASS. */ +enum reg_class +ms1_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 +ms1_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 +ms1_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 +ms1_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 ms1_add_loop (void) +{ + cfun->machine->has_loops++; +} + + +/* Maxium loop nesting depth. */ +#define MAX_LOOP_DEPTH 4 +/* Maxium 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 basic blocks */ +DEF_VEC_P (basic_block); +DEF_VEC_ALLOC_P (basic_block,heap); + +/* And 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 +ms1_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) + ms1_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 +ms1_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 +ms1_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 (ms1_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 instriction, 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 +ms1_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 = ms1_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 += ms1_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 (!ms1_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 (ms1_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 *ms1_labels; + +/* If *X is a label, add INSN to the list of branches for that + label. */ + +static int +ms1_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 = ms1_labels; info; info = info->next) + if (info->label == label) + break; + + if (!info) + { + info = xmalloc (sizeof (*info)); + info->next = ms1_labels; + ms1_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 +ms1_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 +ms1_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), ms1_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 && !INSN_P (next); + next = NEXT_INSN (next)) + continue; + + 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 = ms1_labels; + label; + label = label->next) + if (label->label == prev) + { + for (branch = label->branches; + branch; + branch = branch->next) + { + tmp = ms1_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)) + continue; + + if (GET_CODE (PATTERN (prev)) == SEQUENCE) + { + /* Look at the delay slot. */ + tmp = ms1_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) + { + rtx set = single_set (prev); + + /* A noop set will get deleted in a later split pass, + so we can't count on it for hazard avoidance. */ + if (!set || !set_noop_p (set)) + 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 (ms1_labels) + { + label_info *label = ms1_labels; + branch_info *branch, *next; + + ms1_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 +ms1_machine_reorg (void) +{ + if (cfun->machine->has_loops) + ms1_reorg_loops (dump_file); + + if (ms1_flag_delayed_branch) + dbr_schedule (get_insns (), dump_file); + + if (ms1_cpu == PROCESSOR_MS2) + ms1_reorg_hazard (); +} + +/* Initialize the GCC target structure. */ +const struct attribute_spec ms1_attribute_table[]; + +#undef TARGET_ATTRIBUTE_TABLE +#define TARGET_ATTRIBUTE_TABLE ms1_attribute_table +#undef TARGET_STRUCT_VALUE_RTX +#define TARGET_STRUCT_VALUE_RTX ms1_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 ms1_pass_by_reference +#undef TARGET_MUST_PASS_IN_STACK +#define TARGET_MUST_PASS_IN_STACK ms1_pass_in_stack +#undef TARGET_ARG_PARTIAL_BYTES +#define TARGET_ARG_PARTIAL_BYTES ms1_arg_partial_bytes +#undef TARGET_MACHINE_DEPENDENT_REORG +#define TARGET_MACHINE_DEPENDENT_REORG ms1_machine_reorg + +struct gcc_target targetm = TARGET_INITIALIZER; + +#include "gt-ms1.h" |