diff options
Diffstat (limited to 'gcc/config/d10v/d10v.c')
-rwxr-xr-x | gcc/config/d10v/d10v.c | 3880 |
1 files changed, 3880 insertions, 0 deletions
diff --git a/gcc/config/d10v/d10v.c b/gcc/config/d10v/d10v.c new file mode 100755 index 0000000..27deaf7 --- /dev/null +++ b/gcc/config/d10v/d10v.c @@ -0,0 +1,3880 @@ +/* CYGNUS LOCAL -- meissner/d10v abi change */ +/* Subroutines used for Mitsubishi D10V. + Copyright (C) 1996, 1997, 1998 Free Software Foundation, Inc. + Contributed by Cygnus Support. + +This file is part of GNU CC. + +GNU CC 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. + +GNU CC 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 GNU CC; see the file COPYING. If not, write to +the Free Software Foundation, 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. */ + +#include "config.h" +#include "system.h" +#include "rtl.h" +#include "regs.h" +#include "hard-reg-set.h" +#include "real.h" +#include "insn-config.h" +#include "conditions.h" +#include "insn-flags.h" +#include "output.h" +#include "insn-attr.h" +#include "flags.h" +#include "recog.h" +#include "tree.h" +#include "expr.h" +#include "obstack.h" +#include "except.h" +#include "function.h" + +/* Operands to compare before the appropriate branch is done. */ +struct rtx_def *compare_op0; +struct rtx_def *compare_op1; + +/* Whether or not a hard register can accept a register */ +unsigned char hard_regno_mode_ok[ (int)MAX_MACHINE_MODE ][FIRST_PSEUDO_REGISTER]; + +/* Whether to try and avoid moves between two different modes */ +unsigned char modes_tieable_p[ (NUM_MACHINE_MODES) * (NUM_MACHINE_MODES) ]; + +/* Map register number to smallest register class. */ +enum reg_class regno_reg_class[FIRST_PSEUDO_REGISTER]; + +/* Map class letter into register class */ +enum reg_class reg_class_from_letter[256]; + +/* Values of the -mbranch-cost=n string. */ +int d10v_branch_cost = 1; +char *d10v_branch_cost_string = (char *)0; + +/* Values of the -mcond-exec=n string. */ +int d10v_cond_exec = 4; +char *d10v_cond_exec_string = (char *)0; + +static void d10v_split_constant32 PROTO ((rtx x, int values[])); +static void d10v_split_constant64 PROTO ((rtx x, int values[])); + +/* Cached value of d10v_stack_info */ +static d10v_stack_t *d10v_stack_cache = (d10v_stack_t *)0; + + +/* Sometimes certain combinations of command options do not make + sense on a particular target machine. You can define a macro + `OVERRIDE_OPTIONS' to take account of this. This macro, if + defined, is executed once just after all the command options have + been parsed. + + Don't use this macro to turn on various extra optimizations for + `-O'. That is what `OPTIMIZATION_OPTIONS' is for. */ + +void +override_options () +{ + int regno, i, ok_p; + enum machine_mode mode1, mode2; + + /* Set up the branch cost information */ + if (d10v_branch_cost_string) + d10v_branch_cost = atoi (d10v_branch_cost_string); + + /* Set up max # instructions to use with conditional execution */ + if (d10v_cond_exec_string) + d10v_cond_exec = atoi (d10v_cond_exec_string); + + /* Setup hard_regno_mode_ok/modes_tieable_p */ + for (mode1 = VOIDmode; + (int)mode1 < NUM_MACHINE_MODES; + mode1 = (enum machine_mode)((int)mode1 + 1)) + { + int size = GET_MODE_SIZE (mode1); + int int_p = GET_MODE_CLASS (mode1) == MODE_INT; + + for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++) + { + if (mode1 == CCmode || mode1 == CC_REVmode) + ok_p = (regno == F0_REGNUM); + + else if (CR_P (regno)) + ok_p = 0; + + else if (ACCUM_P (regno)) + ok_p = (mode1 == SImode); + + else if (GPR_P (regno)) + { + if (mode1 == QImode || mode1 == HImode) + ok_p = 1; + else + ok_p = GPR_EVEN_P (regno); + } + else + ok_p = 0; + + hard_regno_mode_ok[ (int)mode1 ][ regno ] = ok_p; + } + + /* A C expression that is nonzero if it is desirable to choose + register allocation so as to avoid move instructions between a + value of mode MODE1 and a value of mode MODE2. + + If `HARD_REGNO_MODE_OK (R, MODE1)' and `HARD_REGNO_MODE_OK (R, + MODE2)' are ever different for any R, then `MODES_TIEABLE_P (MODE1, + MODE2)' must be zero. */ + for (mode2 = VOIDmode; + (int)mode2 <= NUM_MACHINE_MODES; + mode2 = (enum machine_mode)((int)mode2 + 1)) + { + if (mode1 == mode2) + ok_p = 1; + + else if (mode1 == HImode || mode2 == QImode) + ok_p = (mode2 == HImode || mode2 == QImode); + + else + ok_p = 0; + + modes_tieable_p[ ((int)mode1 * (NUM_MACHINE_MODES)) + (int)mode2 ] = ok_p; + } + } + + /* A C expression whose value is a register class containing hard + register REGNO. In general there is more than one such class; + choose a class which is "minimal", meaning that no smaller class + also contains the register. */ + for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++) + { + enum reg_class class; + + switch (regno) + { + case GPR_FIRST + 4: + case GPR_FIRST + 5: + case GPR_FIRST + 6: + case GPR_FIRST + 7: + case GPR_FIRST + 8: + case GPR_FIRST + 9: + case GPR_FIRST + 10: + case GPR_FIRST + 11: + case GPR_FIRST + 12: + case GPR_FIRST + 14: + case GPR_FIRST + 15: + class = EVEN_REGS; + break; + + case ARG_FIRST + 0: /* r0 */ + class = ARG0_REGS; + break; + + case ARG_FIRST + 1: /* r1 */ + class = ARG1_REGS; + break; + + case ARG_FIRST + 2: /* r2 */ + class = ARG2_REGS; + break; + + case ARG_FIRST + 3: /* r3 */ + class = ARG3_REGS; + break; + + case GPR_FIRST + 13: /* r13 */ + class = RETURN_REGS; + break; + + case AP_FIRST: + class = GENERAL_REGS; + break; + + case ACCUM_FIRST + 0: + case ACCUM_FIRST + 1: + class = ACCUM_REGS; + break; + + case CR_FIRST + 7: + class = REPEAT_REGS; + break; + + case CR_FIRST + 0: + case CR_FIRST + 1: + case CR_FIRST + 2: + case CR_FIRST + 3: + case CR_FIRST + 4: + case CR_FIRST + 5: + case CR_FIRST + 6: + case CR_FIRST + 8: + case CR_FIRST + 9: + case CR_FIRST + 10: + case CR_FIRST + 11: + case CR_FIRST + 12: + case CR_FIRST + 13: + case CR_FIRST + 14: + case CR_FIRST + 15: + class = CR_REGS; + break; + + case F0_REGNUM: + class = F0_REGS; + break; + + case F1_REGNUM: + class = F1_REGS; + break; + + case CARRY_REGNUM: + class = CARRY_REGS; + break; + } + + regno_reg_class[regno] = class; + } + + /* A C expression which defines the machine-dependent operand + constraint letters for register classes. If CHAR is such a + letter, the value should be the register class corresponding to + it. Otherwise, the value should be `NO_REGS'. The register + letter `r', corresponding to class `GENERAL_REGS', will not be + passed to this macro; you do not need to handle it. + + The following letters are unavailable, due to being used as + constraints: + '0'..'9' + '<', '>' + 'E', 'F', 'G', 'H' + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P' + 'Q', 'R', 'S', 'T', 'U' + 'V', 'X' + 'g', 'i', 'm', 'n', 'o', 'p', 'r', 's' */ + + for (i = 0; i < 256; i++) + reg_class_from_letter[i] = NO_REGS; + + reg_class_from_letter['C'] = CARRY_REGS; + reg_class_from_letter['a'] = ACCUM_REGS; + reg_class_from_letter['b'] = ARG0_REGS; + reg_class_from_letter['c'] = CR_REGS; + reg_class_from_letter['d'] = GENERAL_REGS; + reg_class_from_letter['e'] = EVEN_REGS; + reg_class_from_letter['f'] = F0_REGS; + reg_class_from_letter['h'] = ARG1_REGS; + reg_class_from_letter['j'] = ARG2_REGS; + reg_class_from_letter['l'] = RETURN_REGS; + reg_class_from_letter['q'] = ARG3_REGS; + reg_class_from_letter['x'] = CC_REGS; + reg_class_from_letter['y'] = F1_REGS; + reg_class_from_letter['z'] = REPEAT_REGS; +} + + +/* Define this macro if references to a symbol must be treated + differently depending on something about the variable or function + named by the symbol (such as what section it is in). + + The macro definition, if any, is executed immediately after the + rtl for DECL has been created and stored in `DECL_RTL (DECL)'. + The value of the rtl will be a `mem' whose address is a + `symbol_ref'. + + The usual thing for this macro to do is to record a flag in the + `symbol_ref' (such as `SYMBOL_REF_FLAG') or to store a modified + name string in the `symbol_ref' (if one bit is not enough + information). */ + +void +encode_section_info (decl) + tree decl; +{ + /* Mark functions so that we use the @word modifier to get a word + address instead of a byte address. We use the side_effects bit + on the identifier node to indicate word addresses are needed. */ + if (TREE_CODE (decl) == FUNCTION_DECL) + { + rtx symref = XEXP (DECL_RTL (decl), 0); + tree symid = get_identifier (XSTR (symref, 0)); + + SYMBOL_REF_FLAG (symref) = TRUE; + TREE_SIDE_EFFECTS (symid) = TRUE; + } +} + + +/* Return true if the register is a GPR */ + +int +gpr_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (GET_CODE (op) == REG) + { + int regno = REGNO (op); + if (regno >= 0 && regno < FIRST_PSEUDO_REGISTER) + return GPR_P (regno); + + return register_operand (op, mode); + } + + else if (GET_CODE (op) == SUBREG) + { + rtx inner = SUBREG_REG (op); + if (GET_CODE (inner) == REG) + { + int regno = REGNO (SUBREG_REG (op)); + if (regno >= 0 && regno < FIRST_PSEUDO_REGISTER) + return GPR_P (regno); + + return register_operand (op, mode); + } + + else if (GET_CODE (inner) == MEM && !reload_completed) + return d10v_legitimate_address_p (GET_MODE (inner), inner, 0); + } + + return FALSE; +} + +/* Return true if the register is an accumulator */ + +int +accum_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (GET_CODE (op) == REG) + { + int regno = REGNO (op); + if (regno >= 0 && regno < FIRST_PSEUDO_REGISTER) + return ACCUM_P (regno); + + return register_operand (op, mode); + } + + else if (GET_CODE (op) == SUBREG) + { + rtx inner = SUBREG_REG (op); + if (GET_CODE (inner) == REG) + { + int regno = REGNO (SUBREG_REG (op)); + if (regno >= 0 && regno < FIRST_PSEUDO_REGISTER) + return ACCUM_P (regno); + + return register_operand (op, mode); + } + + else if (GET_CODE (inner) == MEM && !reload_completed) + return d10v_legitimate_address_p (GET_MODE (inner), inner, 0); + } + + return FALSE; +} + +/* Return true if an operand is a register or is the constant 0. */ + +int +reg_or_0_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (GET_CODE (op) == CONST_INT) + return INTVAL (op) == 0; + + else + return gpr_operand (op, mode); +} + +/* Return true if an operand is a register or a signed 16-bit constant. */ + +int +arith16_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (GET_CODE (op) == CONST_INT) + return IN_RANGE_P (INTVAL (op), -32768, 32767); + + else + return gpr_operand (op, mode); +} + +/* Return true if an operand is a register or a 4-bit unsigned constants, + or 4-bit negative unsigned constants. */ + +int +arith_4bit_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (GET_CODE (op) == CONST_INT) + return IN_RANGE_P (INTVAL (op), -16, 16) && INTVAL (op) != 0; + + else + return gpr_operand (op, mode); +} + +/* Return true if an operand is a register or a non negative constant. */ + +int +arith_nonnegative_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (GET_CODE (op) == CONST_INT) + return (INTVAL (op) >= 0); + + else + return gpr_operand (op, mode); +} + +/* Return true if an operand is a register or a signed 32-bit constant. */ + +int +arith32_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (GET_CODE (op) == CONST_INT) + return TRUE; + + else + return gpr_operand (op, mode); +} + +/* Return true if an operand is a register or a signed 64-bit constant. */ + +int +arith64_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (GET_CODE (op) == CONST_INT || GET_CODE (op) == CONST_DOUBLE) + return TRUE; + + else + return gpr_operand (op, mode); +} + +/* Return true if an operand is a register or a constant with the lower 16-bits of 0. */ + +int +arith_lower0_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (GET_CODE (op) == CONST_INT) + return ((INTVAL (op) & 0xffff) == 0); + + else + return gpr_operand (op, mode); +} + +int +ldi_shift_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (TARGET_SMALL_INSNS && optimize && GET_CODE (op) == CONST_INT) + { + HOST_WIDE_INT value = INTVAL (op); + int i; + + if (IN_RANGE_P (value, -8, 7)) + return FALSE; + + for (i = 1; i < 16; i++) + { + if (((value >> i) << i) == value && IN_RANGE_P (value >> i, -8, 7)) + return i; + } + } + + return FALSE; +} + +/* Return true if the operand is either the PC, a return, or a label_ref. */ + +int +pc_or_label_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + if (op == pc_rtx) + return TRUE; + + if (GET_CODE (op) == LABEL_REF) + return TRUE; + + if (GET_CODE (op) == RETURN && direct_return () && (d10v_stack_info ())->total_size == 0) + return TRUE; + + return FALSE; +} + +/* Return true if a memory operand is a short memory operand. */ + +int +short_memory_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + rtx addr; + + if (GET_CODE (op) != MEM) + return FALSE; + + addr = XEXP (op, 0); + switch (GET_CODE (addr)) + { + default: + break; + + case POST_INC: + case POST_DEC: + case PRE_DEC: + case REG: + return TRUE; + } + + return FALSE; +} + +/* Return true if an operand is simple, suitable for use in a conditional move */ + +int +cond_move_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (mode != QImode && mode != HImode && mode != SImode && mode != SFmode) + return FALSE; + + else if (GET_CODE (op) == REG || GET_CODE (op) == SUBREG) + return gpr_operand (op, mode) || (mode == SImode && accum_operand (op, mode)); + + else if (GET_CODE (op) == CONST_INT && (mode == QImode || mode == HImode)) + return IN_RANGE_P (INTVAL (op), -8, 7); + + /* Don't allow post dec/inc, since we might not get the side effects correct. */ + else if (GET_CODE (op) == MEM) + return (GET_CODE (XEXP (op, 0)) == REG); + + return FALSE; +} + +/* Return true if an operand is simple, suitable for use in conditional execution. + Unlike cond_move, we can allow auto inc/dec. */ + +int +cond_exec_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (mode != QImode && mode != HImode && mode != SImode && mode != SFmode) + return FALSE; + + else if (GET_CODE (op) == REG || GET_CODE (op) == SUBREG) + return gpr_operand (op, mode) || (mode == SImode && accum_operand (op, mode)); + + else if (GET_CODE (op) == CONST_INT && (mode == QImode || mode == HImode)) + return IN_RANGE_P (INTVAL (op), -8, 7); + + else if (GET_CODE (op) == MEM) + return short_memory_operand (op, mode); + + return FALSE; +} + +/* Return true if an operand is F0. */ + +int +carry_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (GET_CODE (op) != REG) + return FALSE; + + if (GET_MODE (op) != CCmode && GET_MODE (op) != CC_REVmode) + return FALSE; + + if (REGNO (op) != CARRY_REGNUM) + return FALSE; + + return 1; +} + +int +f0_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (GET_CODE (op) != REG) + return FALSE; + + if (GET_MODE (op) != CCmode && GET_MODE (op) != CC_REVmode) + return FALSE; + + if (REGNO (op) != F0_REGNUM) + return FALSE; + + return 1; +} + +/* Return true if an operand is F1. */ + +int +f1_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (GET_CODE (op) != REG) + return FALSE; + + if (GET_MODE (op) != CCmode && GET_MODE (op) != CC_REVmode) + return FALSE; + + if (REGNO (op) != F1_REGNUM) + return FALSE; + + return 1; +} + +/* Return true if an operand is F0 or F1. */ + +int +f_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (GET_CODE (op) != REG) + return FALSE; + + if (GET_MODE (op) != CCmode && GET_MODE (op) != CC_REVmode) + return FALSE; + + if (REGNO (op) != F0_REGNUM && REGNO (op) != F1_REGNUM) + return FALSE; + + return 1; +} + +/* Return true if the code is a test of f0 */ + +int +f0_compare_operator (op, mode) + rtx op; + enum machine_mode mode; +{ + int i; + rtx x; + + if (GET_MODE (op) != CCmode && GET_MODE (op) != CC_REVmode) + return FALSE; + + if (GET_CODE (op) != NE && GET_CODE (op) != EQ) + return FALSE; + + x = XEXP (op, 0); + if (GET_CODE (x) != REG || REGNO (x) != F0_REGNUM) + return FALSE; + + x = XEXP (op, 1); + if (GET_CODE (x) != CONST_INT || INTVAL (x) != 0) + return FALSE; + + return TRUE; +} + +/* Return true if the code is a relational operations (EQ, LE, etc.) */ + +int +compare_operator (op, mode) + rtx op; + enum machine_mode mode; +{ + int i; + rtx x; + + if (GET_MODE (op) != CCmode && GET_MODE (op) != CC_REVmode) + return FALSE; + + if (GET_RTX_CLASS (GET_CODE (op)) != '<') + return FALSE; + + /* Don't allow paradoxical SUBREGs */ + for (i = 0; i < 2; i++) + { + x = XEXP (op, i); + while (GET_CODE (x) == SUBREG) + x = SUBREG_REG (x); + + if (!(GET_CODE (x) == REG || (i == 1 && GET_CODE (x) == CONST_INT))) + return FALSE; + } + + return TRUE; +} + +/* Return true if the code is a equality operation (EQ, NE) */ + +int +equality_operator (op, mode) + rtx op; + enum machine_mode mode; +{ + int i; + rtx x; + + if (GET_MODE (op) != CCmode && GET_MODE (op) != CC_REVmode) + return FALSE; + + switch (GET_CODE (op)) + { + default: + return FALSE; + + case EQ: + case NE: + break; + } + + /* Don't allow paradoxical SUBREGs */ + for (i = 0; i < 2; i++) + { + x = XEXP (op, i); + while (GET_CODE (x) == SUBREG) + x = SUBREG_REG (x); + + if (!(GET_CODE (x) == REG || (i == 1 && GET_CODE (x) == CONST_INT))) + return FALSE; + } + + return TRUE; +} + +/* Return true if the code is a signed relational operations (LT, LE, etc.) */ + +int +signed_compare_operator (op, mode) + rtx op; + enum machine_mode mode; +{ + int i; + rtx x; + + if (GET_MODE (op) != CCmode && GET_MODE (op) != CC_REVmode) + return FALSE; + + switch (GET_CODE (op)) + { + default: + return FALSE; + + case LT: + case LE: + case GT: + case GE: + break; + } + + /* Don't allow paradoxical SUBREGs */ + for (i = 0; i < 2; i++) + { + x = XEXP (op, i); + while (GET_CODE (x) == SUBREG) + x = SUBREG_REG (x); + + if (!(GET_CODE (x) == REG || (i == 1 && GET_CODE (x) == CONST_INT))) + return FALSE; + } + + return TRUE; +} + +/* Return true if the code is a unsigned relational operations (LEU, etc.) */ + +int +unsigned_compare_operator (op, mode) + rtx op; + enum machine_mode mode; +{ + int i; + rtx x; + + if (GET_MODE (op) != CCmode && GET_MODE (op) != CC_REVmode) + return FALSE; + + switch (GET_CODE (op)) + { + default: + return FALSE; + + case LTU: + case LEU: + case GTU: + case GEU: + break; + } + + /* Don't allow paradoxical SUBREGs */ + for (i = 0; i < 2; i++) + { + x = XEXP (op, i); + while (GET_CODE (x) == SUBREG) + x = SUBREG_REG (x); + + if (!(GET_CODE (x) == REG || (i == 1 && GET_CODE (x) == CONST_INT))) + return FALSE; + } + + return TRUE; +} + +/* Return true if the unary operator can go inside of a exef0{f,t} || operation. */ + +int +unary_parallel_operator (op, mode) + rtx op; + enum machine_mode mode; +{ + rtx op0; + + /* Only do this after register allocation, so that we can look at the register # */ + if (!reload_completed) + return FALSE; + + if (GET_RTX_CLASS (GET_CODE (op)) != '1') + return FALSE; + + op0 = XEXP (op, 0); + while (GET_CODE (op0) == SUBREG) + op0 = SUBREG_REG (op0); + + switch (GET_CODE (op)) + { + case NEG: + case NOT: + case SIGN_EXTEND: + case ZERO_EXTEND: + if (GET_MODE (op) == HImode && GET_CODE (op0) == REG && GPR_P (REGNO (op0))) + return TRUE; + + break; + } + + return FALSE; +} + +/* Return true if the binary operator can go inside of a exef0{f,t} || operation. */ + +int +binary_parallel_operator (op, mode) + rtx op; + enum machine_mode mode; +{ + rtx op0, op1; + + /* Only do this after register allocation, so that we can look at the register # */ + if (!reload_completed) + return FALSE; + + if (GET_RTX_CLASS (GET_CODE (op)) != '2' && GET_RTX_CLASS (GET_CODE (op)) != 'c') + return FALSE; + + op0 = XEXP (op, 0); + op1 = XEXP (op, 1); + + while (GET_CODE (op0) == SUBREG) + op0 = SUBREG_REG (op0); + + while (GET_CODE (op1) == SUBREG) + op1 = SUBREG_REG (op1); + + if (GET_CODE (op0) != REG) + return FALSE; + + /* reg1 op= reg2 */ + if (GET_CODE (op1) == REG) + { + switch (GET_CODE (op)) + { + case PLUS: + case MINUS: + if (GET_MODE (op) == SImode && GPR_P (REGNO (op0)) && GPR_P (REGNO (op1))) + return TRUE; + + /* fall through */ + + case MULT: + case AND: + case IOR: + case XOR: + case ASHIFT: + case ASHIFTRT: + case LSHIFTRT: + return (GET_MODE (op) == HImode && GPR_P (REGNO (op0)) && GPR_P (REGNO (op1))); + } + } + + /* reg op= constant */ + else if (GET_CODE (op1) == CONST_INT) + { + HOST_WIDE_INT value = INTVAL (op1); + int log; + + if (GET_MODE (op) == HImode) + switch (GET_CODE (op)) + { + case PLUS: + case MINUS: + return IN_RANGE_P (value, -16, 16); + + case IOR: + case XOR: + if (value == 0) + return TRUE; + + log = exact_log2 (value); + return IN_RANGE_P (log, 0, 15); + + case AND: + if ((value & 0xffff) == 0xffff) + return TRUE; + + log = exact_log2 (~value); + return IN_RANGE_P (log, 0, 15); + } + } + + return FALSE; +} + +/* Return true if the {sign,zero} extend operator from memory can go inside + of a exef0{f,t} || operation. */ + +int +extend_parallel_operator (op, mode) + rtx op; + enum machine_mode mode; +{ + /* Only do this after register allocation, so that we can look at the register # */ + if (!reload_completed) + return FALSE; + + if (GET_RTX_CLASS (GET_CODE (op)) != '1') + return FALSE; + + switch (GET_CODE (op)) + { + case SIGN_EXTEND: + case ZERO_EXTEND: + if (GET_MODE (op) == HImode && short_memory_operand (XEXP (op, 0), QImode)) + return TRUE; + + break; + } + + return FALSE; +} + +/* Return true if the {smin,smax} operator can go inside of a exef0{f,t} || operation. */ + +int +minmax_parallel_operator (op, mode) + rtx op; + enum machine_mode mode; +{ + /* Only do this after register allocation, so that we can look at the register # */ + if (!reload_completed) + return FALSE; + + if (GET_RTX_CLASS (GET_CODE (op)) != 'c') + return FALSE; + + switch (GET_CODE (op)) + { + case SMIN: + case SMAX: + if (GET_MODE (op) == HImode + && gpr_operand (XEXP (op,0), mode) + && gpr_operand (XEXP (op, 1))) + return TRUE; + + if (GET_MODE (op) == SImode + && accum_operand (XEXP (op,0), mode) + && accum_operand (XEXP (op, 1))) + return TRUE; + + break; + } + + return FALSE; +} + + +/* Return true if a 32-bit integer constant can be loaded by two parallel + ldi constants. */ + +int +parallel_ldi (value) + HOST_WIDE_INT value; +{ + int low = SIGN_EXTEND_SHORT (value); + int high = SIGN_EXTEND_SHORT (value >> 16); + + return IN_RANGE_P (low, -8, 7) && IN_RANGE_P (high, -8, 7); +} + + +/* Return true if the two operands reference adjacement memory locations so that + a ld2w/st2w instruction can be used. The insn argument is the last insn of + the peephole, so that REG_DEAD notes are useful. */ + +int +adjacent_memory_operands (op1, op2, insn) + rtx op1; + rtx op2; + rtx insn; +{ + rtx addr1, addr2; + rtx const1, const2; + enum rtx_code code1, code2; + + if (GET_CODE (op1) != MEM || GET_MODE (op1) != HImode) + return FALSE; + + if (GET_CODE (op2) != MEM || GET_MODE (op2) != HImode) + return FALSE; + + addr1 = XEXP (op1, 0); + addr2 = XEXP (op2, 0); + code1 = GET_CODE (addr1); + code2 = GET_CODE (addr2); + if (((code1 == POST_INC && code2 == POST_INC)) + || ((code1 == POST_DEC && code2 == POST_DEC)) + || ((code1 == PRE_DEC && code2 == PRE_DEC))) + { + addr1 = XEXP (addr1, 0); + addr2 = XEXP (addr2, 0); + return (GET_CODE (addr1) == REG && GET_CODE (addr2) == REG + && REGNO (addr1) == REGNO (addr2)); + } + + else if ((code1 == POST_INC || code1 == POST_DEC || code1 == PRE_DEC) + && code2 != POST_INC && code2 != POST_DEC && code2 != PRE_DEC) + { + addr1 = XEXP (addr1, 0); + return (GET_CODE (addr1) == REG && code2 == REG + && REGNO (addr1) == REGNO (addr2) + && find_reg_note (insn, REG_DEAD, addr1)); + } + + else if (code1 == POST_INC || code1 == POST_DEC || code1 == PRE_DEC + || code2 == POST_INC || code2 == POST_DEC || code2 == PRE_DEC) + return FALSE; + + if (code1 == CONST) + addr1 = XEXP (addr1, 0); + + if (code2 == CONST) + code2 = GET_CODE (addr2); + + const1 = const0_rtx; + const2 = const0_rtx; + addr1 = eliminate_constant_term (addr1, &const1); + addr2 = eliminate_constant_term (addr2, &const2); + + if (!rtx_equal_p (addr1, addr2)) + return FALSE; + + if (GET_CODE (const1) == CONST_INT + && GET_CODE (const2) == CONST_INT + && INTVAL (const1) == INTVAL (const2) - 2) + return TRUE; + + return FALSE; +} + + +/* Expand a divmod operation */ + +void +d10v_expand_divmod (operands, unsigned_p) + rtx operands[]; + int unsigned_p; +{ + rtx ret = emit_library_call_value (gen_rtx (SYMBOL_REF, Pmode, (unsigned_p) ? "__udivmodhi4" : "__divmodhi4"), + NULL_RTX, TRUE, SImode, 2, + operands[1], HImode, + operands[2], HImode); + + emit_move_insn (operands[0], gen_rtx (SUBREG, HImode, ret, 1)); + emit_move_insn (operands[3], gen_rtx (SUBREG, HImode, ret, 0)); +} + + +/* A C statement to initialize the variable parts of a trampoline. + ADDR is an RTX for the address of the trampoline; FNADDR is an RTX + for the address of the nested function; STATIC_CHAIN is an RTX for + the static chain value that should be passed to the function when + it is called. */ + +void +initialize_trampoline (addr, fnaddr, static_chain) + rtx addr; + rtx fnaddr; + rtx static_chain; +{ + rtx byte_addr = gen_reg_rtx (Pmode); + rtx reg1 = gen_reg_rtx (Pmode); + rtx reg2 = gen_reg_rtx (Pmode); + + emit_move_insn (byte_addr, addr); + + /* ldi.l r7,<static chain> */ + emit_move_insn (gen_rtx (MEM, HImode, gen_rtx (POST_INC, HImode, byte_addr)), + GEN_INT (0xE000 | ((STATIC_CHAIN_REGNUM - GPR_FIRST) << 4))); + + emit_move_insn (gen_rtx (MEM, HImode, gen_rtx (POST_INC, HImode, byte_addr)), + static_chain); + + /* bra.l <function> */ + emit_move_insn (gen_rtx (MEM, HImode, gen_rtx (POST_INC, HImode, byte_addr)), + GEN_INT (0xE400)); + + emit_move_insn (reg1, fnaddr); + emit_move_insn (reg2, addr); + emit_insn (gen_subhi3 (reg1, reg1, reg2)); + emit_move_insn (gen_rtx (MEM, HImode, byte_addr), reg1); + + error ("Trampolines currently don't work"); +} + + +/* Emit the appropriate comparison code for two operands. Return TRUE if + F0 contains a true value for the condition to be successful, FALSE if + not. + + Arguments: + + code Which comparison to emit + op{0,1} Operands + temp Temporary register that we can clobber (not used right now) + insn Whole insn + mode Mode of the operands + output_p Whether to actually emit code or just return true/false. */ + +int +emit_comparison (code, op0, op1, temp, insn, mode, output_p) + enum rtx_code code; + rtx op0, op1, temp, insn; + enum machine_mode mode; + int output_p; +{ + int ret = FALSE; + char *template = (char *)0; + int op1_int_p = CONSTANT_P (op1); + rtx xop[10]; + + /* Skip subregs that we find. */ + while (GET_CODE (op0) == SUBREG) + op0 = SUBREG_REG (op0); + + while (GET_CODE (op1) == SUBREG) + op1 = SUBREG_REG (op1); + + xop[0] = op0; + xop[1] = op1; + xop[2] = temp; + + /* 16-bit comparisons */ + if (mode == HImode) + { + switch (code) + { + default: + break; + + case EQ: + template = (!op1_int_p) ? "cmpeq %0,%1" : "cmpeqi %0,%1"; + ret = TRUE; + break; + + case NE: + template = (!op1_int_p) ? "cmpeq %0,%1" : "cmpeqi %0,%1"; + ret = FALSE; + break; + + case LT: + template = (!op1_int_p) ? "cmp %0,%1" : "cmpi %0,%1"; + ret = TRUE; + break; + + case LE: + if (!op1_int_p) + { /* convert LE x,y to !GT x,y (ie, LT y,x) */ + template = "cmp %1,%0"; + ret = FALSE; + break; + } + else if (GET_CODE (op1) != CONST_INT || INTVAL (op1) != 32767) + { + /* If some sum of a symbol/label and a constant ends up + as 32768, it would be the linker's task to fix that up. */ + if (output_p) + xop[3] = plus_constant_for_output (op1, 1); + + template = "cmpi %0,%3"; + ret = TRUE; + break; + } + else + { + template = "cmpeqi %0,%1"; + ret = TRUE; + break; + } + + case GT: + if (!op1_int_p) + { /* convert GT x,y to LT y,x */ + template = "cmp %1,%0"; + ret = TRUE; + break; + } + else if (GET_CODE (op1) != CONST_INT || INTVAL (op1) != 32767) + { + if (output_p) + xop[3] = plus_constant_for_output (op1, 1); + + template = "cmpi %0,%3"; + ret = FALSE; + break; + } + else + { + template = "cmpeq %0,%0"; + ret = FALSE; + break; + } + + case GE: + template = (!op1_int_p) ? "cmp %0,%1" : "cmpi %0,%1"; + ret = FALSE; + break; + + case LTU: + template = (!op1_int_p) ? "cmpu %0,%1" : "cmpui %0,%1"; + ret = TRUE; + break; + + case LEU: + if (!op1_int_p) + { /* convert LEU x,y to !GTU x,y (ie, LTU y,x) */ + template = "cmpu %1,%0"; + ret = FALSE; + break; + } + else if (GET_CODE (op1) != CONST_INT || ~INTVAL (op1) & 65535) + { + if (output_p) + xop[3] = plus_constant_for_output (op1, 1); + + template = "cmpui %0,%3"; + ret = TRUE; + break; + } + else + { + template = "cmpeq %0,%0"; + ret = TRUE; + break; + } + + case GTU: + if (!op1_int_p) + { /* convert GTU x,y to LTU y,x */ + template = "cmpu %1,%0"; + ret = TRUE; + break; + } + else if (GET_CODE (op1) != CONST_INT || ~INTVAL (op1) & 65535) + { /* convert GTU x,y to !LTU x,y+1 */ + if (output_p) + xop[3] = plus_constant_for_output (op1, 1); + + template = "cmpui %0,%3"; + ret = FALSE; + break; + } + else + { + /* Convert GTU x,65535 to !EQ x,x */ + template = "cmpeq %0,%0"; + ret = FALSE; + break; + } + + case GEU: + template = (!op1_int_p) ? "cmpu %0,%1" : "cmpui %0,%1"; + ret = FALSE; + break; + } + } + + /* 32-bit comparisons in the accumulators. */ + else if (mode == SImode + && GET_CODE (op0) == REG && ACCUM_P (REGNO (op0)) + && GET_CODE (op1) == REG && ACCUM_P (REGNO (op1))) + { + switch (code) + { + default: + break; + + case EQ: + template = "cmpeq %0,%1"; + ret = TRUE; + break; + + case NE: + template = "cmpeq %0,%1"; + ret = FALSE; + break; + + case LT: + template = "cmp %0,%1"; + ret = TRUE; + break; + + case LE: + /* convert LE x,y to !GT x,y (ie, LT y,x) */ + template = "cmp %1,%0"; + ret = FALSE; + break; + + case GT: + /* convert GT x,y to LT y,x */ + template = "cmp %1,%0"; + ret = TRUE; + break; + + case GE: + template = "cmp %0,%1"; + ret = FALSE; + break; + } + } + + /* 32-bit comparisons in the general purpose registers. */ + else if (mode == SImode + && GET_CODE (op0) == REG && GPR_OR_PSEUDO_P (REGNO (op0)) + && GET_CODE (op1) == REG && GPR_OR_PSEUDO_P (REGNO (op1))) + { + switch (code) + { + default: + break; + + case EQ: + template = "cmpeq %U0,%U1\n\texef0t || cmpeq %L0,%L1"; + ret = TRUE; + break; + + case NE: + template = "cmpeq %U0,%U1\n\texef0t || cmpeq %L0,%L1"; + ret = FALSE; + break; + + case LT: + template = "cmpeq %U0,%U1\n\tcmp %U0,%U1\n\texef1t || cmpu %L0,%L1"; + ret = TRUE; + break; + + case LE: + /* convert LE x,y to !GT x,y (ie, LT y,x) */ + template = "cmpeq %U1,%U0\n\tcmp %U1,%U0\n\texef1t || cmpu %L1,%L0"; + ret = FALSE; + break; + + case GT: + /* convert GT x,y to LT y,x */ + template = "cmpeq %U1,%U0\n\tcmp %U1,%U0\n\texef1t || cmpu %L1,%L0"; + ret = TRUE; + break; + + case GE: + /* convert GE x,y to !LT x,y */ + template = "cmpeq %U0,%U1\n\tcmp %U0,%U1\n\texef1t || cmpu %L0,%L1"; + ret = FALSE; + break; + + case LTU: + template = "cmpeq %U0,%U1\n\tcmpu %U0,%U1\n\texef1t || cmpu %L0,%L1"; + ret = TRUE; + break; + + case LEU: + /* convert LEU x,y to !GTU x,y (ie, LTU y,x) */ + template = "cmpeq %U1,%U0\n\tcmpu %U1,%U0\n\texef1t || cmpu %L1,%L0"; + ret = FALSE; + break; + + case GTU: + /* convert GTU x,y to LTU y,x */ + template = "cmpeq %U1,%U0\n\tcmpu %U1,%U0\n\texef1t || cmpu %L1,%L0"; + ret = TRUE; + break; + + case GEU: + /* convert GEU x,y to !LTU x,y */ + template = "cmpeq %U0,%U1\n\tcmpu %U0,%U1\n\texef1t || cmpu %L0,%L1"; + ret = FALSE; + break; + } + } + + /* 32-bit comparison against 0 in the general purpose registers. */ + else if (mode == SImode + && GET_CODE (op0) == REG && GPR_OR_PSEUDO_P (REGNO (op0)) + && GET_CODE (op1) == CONST_INT && INTVAL (op1) == 0) + { + switch (code) + { + default: + break; + + case EQ: + case LEU: /* convert LEU x,0 to EQ x,0 */ + template = "cmpeqi %U0,0\n\texef0t || cmpeqi %L0,0"; + ret = TRUE; + break; + + case NE: + case GTU: /* convert GTU x,0 to NE x,0 */ + template = "cmpeqi %U0,0\n\texef0t || cmpeqi %L0,0"; + ret = FALSE; + break; + + case LT: + template = "cmpi %U0,0"; + ret = TRUE; + break; + + case LE: + template = "cmpeqi %U0,0\n\tcmpi %U0,0\n\texef1t || cmpeqi %L0,0"; + ret = TRUE; + break; + + case GT: + /* convert GT x,0 to !LE x,0 */ + template = "cmpeqi %U0,0\n\tcmpi %U0,0\n\texef1t || cmpeqi %L0,0"; + ret = FALSE; + break; + + case GE: + /* convert GE x,0 to !LT x,0 */ + template = "cmpi %U0,0\n"; + ret = FALSE; + break; + + case LTU: + /* LTU x,0 is always false */ + template = "cmpeq %U0,%U0"; + ret = FALSE; + break; + + case GEU: + /* GEU x,0 is always true */ + template = "cmpeq %U0,%U0"; + ret = TRUE; + break; + } + } + + /* 32-bit comparison against a constant with -8..7 in the lower 16 bits */ + else if (mode == SImode + && GET_CODE (op0) == REG && GPR_OR_PSEUDO_P (REGNO (op0)) + && GET_CODE (op1) == CONST_INT + && IN_RANGE_P (SIGN_EXTEND_SHORT (INTVAL (op1)), -8, 7)) + { + switch (code) + { + default: + break; + + case EQ: + template = "cmpeqi %U0,%U1\n\texef0t || cmpeqi %L0,%L1"; + ret = TRUE; + break; + + case NE: + template = "cmpeqi %U0,%U1\n\texef0t || cmpeqi %L0,%L1"; + ret = FALSE; + break; + } + } + + /* Abort on unhandled cases. */ + if (!template) + { + if (!insn) + insn = gen_rtx (SET, VOIDmode, + gen_rtx (REG, CCmode, F0_REGNUM), + gen_rtx (code, CCmode, op0, op1)); + + fatal_insn ("emit_comparison:", insn); + return TRUE; + } + + /* If we are outputing, do it now. */ + if (output_p) + output_asm_insn (template, xop); + + return ret; +} + + +/* Returns a mode from class `MODE_CC' to be used when comparison + operation code OP is applied to rtx X and Y. For example, on the + Sparc, `SELECT_CC_MODE' is defined as (see *note Jump Patterns::. + for a description of the reason for this definition) + + #define SELECT_CC_MODE(OP,X,Y) \ + (GET_MODE_CLASS (GET_MODE (X)) == MODE_FLOAT \ + ? ((OP == EQ || OP == NE) ? CCFPmode : CCFPEmode) \ + : ((GET_CODE (X) == PLUS || GET_CODE (X) == MINUS \ + || GET_CODE (X) == NEG) \ + ? CC_NOOVmode : CCmode)) + + You need not define this macro if `EXTRA_CC_MODES' is not defined. */ +int +select_cc_mode (op, x, y) + enum rtx_code op; + rtx x; + rtx y; +{ + int ret = emit_comparison (op, x, y, NULL_RTX, NULL_RTX, GET_MODE (x), FALSE); + + fprintf (stderr, "select_cc_mode: emit_comparison returned %d\n", ret); + debug_rtx (x); + debug_rtx (y); + + return (ret ? (int)CCmode : (int)CC_REVmode); +} + + +/* Emit the code to set F0 based on comparing op0 and op1. Use CCmode for + comparisons that set F0 to 1 if the comparison is true, and CC_REVmode + for comparisons that set F0 to 0 if the comparison is true. */ + +Rtx +expand_comparison (code, op0, op1, reg_p) + enum rtx_code code; + rtx op0; + rtx op1; + rtx *reg_p; +{ + rtx f0, seq; + enum machine_mode mode; + + start_sequence (); + if (GET_MODE (op0) == DImode) + { + int unsigned_p = (code == GTU || code == GEU || code == LTU || code == LEU); + + if (op1 == CONST0_RTX (DImode)) + op0 = emit_library_call_value (gen_rtx (SYMBOL_REF, Pmode, (unsigned_p) ? "__ucmpdi0" : "__cmpdi0"), + NULL_RTX, TRUE, HImode, 1, op0, DImode); + else + op0 = emit_library_call_value (gen_rtx (SYMBOL_REF, Pmode, (unsigned_p) ? "__ucmpdi" : "__cmpdi"), + NULL_RTX, TRUE, HImode, 2, + op0, DImode, + op1, DImode); + + op1 = const1_rtx; + } + + mode = (emit_comparison (code, op0, op1, NULL_RTX, NULL_RTX, GET_MODE (op0), FALSE) ? CCmode : CC_REVmode); + f0 = gen_rtx (REG, mode, F0_REGNUM); + + emit_insn (gen_rtx (SET, VOIDmode, f0, gen_rtx (code, mode, op0, op1))); + seq = gen_sequence (); + end_sequence (); + + *reg_p = f0; + return seq; +} + + +/* Emit the instructions to move 1 word. */ + +char * +emit_move_word (op0, op1, insn) + rtx op0; + rtx op1; + rtx insn; +{ + char *ret = ""; + int shift; + + if (REG_P (op0) && REG_P (op1)) + { + int reg0 = REGNO (op0); + int reg1 = REGNO (op1); + + if (GPR_P (reg0) && GPR_P (reg1)) + ret = "mv %0,%1"; + + else if (GPR_P (reg0) && CR_P (reg1)) + ret = "mvfc %0,%1"; + + else if (CR_P (reg0) && GPR_P (reg1)) + ret = "mvtc %1,%0"; + + else if (GPR_P (reg0) && reg1 == F0_REGNUM) + ret = "setf0t %0"; + + else + fatal_insn ("emit_move_word:", insn); + } + + else if (REG_P (op0) && GPR_P (REGNO (op0)) && GET_CODE (op1) == MEM) + ret = "ld %0,%M1"; + + else if (GET_CODE (op0) == MEM && REG_P (op1) && GPR_P (REGNO (op1))) + ret = "st %1,%M0"; + + else if (GET_CODE (op0) == MEM && GET_CODE (op1) == CONST_INT && INTVAL (op1) == 0) + ret = "st %.,%M0"; + + /* XXX: We should do LABEL_REF's that are not part switch statements with the + @word modifier, but if we do, that breaks switch statements which need to + index into the array of labels. Using casesi is clumsy because it converts + the switch value to SI before the call. */ + else if (REG_P (op0) && GPR_P (REGNO (op0)) + && GET_CODE (op1) == SYMBOL_REF && SYMBOL_REF_FLAG (op1)) + ret = "ldi %0,%1@word"; + + else if (REG_P (op0) && GPR_P (REGNO (op0)) + && GET_CODE (op1) == CONST_INT + && ldi_shift_operand (op1, HImode)) + ret = "ldi %0,%s1\n\tslli %0,%S1"; + + else if (REG_P (op0) && GPR_P (REGNO (op0)) + && (GET_CODE (op1) == CONST_INT + || GET_CODE (op1) == SYMBOL_REF + || GET_CODE (op1) == LABEL_REF + || GET_CODE (op1) == CONST)) + ret = "ldi %0,%1"; + + else + fatal_insn ("emit_move_word:", insn); + + return ret; +} + + + +/* Emit the instructions to move 2 words. */ + +char * +emit_move_2words (op0, op1, insn) + rtx op0; + rtx op1; + rtx insn; +{ + char *ret = ""; + + if (REG_P (op0) && REG_P (op1)) + { + int reg0 = REGNO (op0); + int reg1 = REGNO (op1); + + if (GPR_P (reg0) && GPR_P (reg1)) + ret = "mv2w %0,%1"; + + else if (GPR_P (reg0) && ACCUM_P (reg1)) + ret = "mv2wfac %0,%1"; + + else if (ACCUM_P (reg0) && GPR_P (reg1)) + ret = "mv2wtac %1,%0"; + + else + fatal_insn ("emit_move_2words:", insn); + } + + else if (REG_P (op0) && GPR_P (REGNO (op0)) && GET_CODE (op1) == MEM) + ret = "ld2w %0,%M1"; + + else if (GET_CODE (op0) == MEM && REG_P (op1) && GPR_P (REGNO (op1))) + ret = "st2w %1,%M0"; + + else if (REG_P (op0) && GPR_P (REGNO (op0)) + && (GET_CODE (op1) == CONST_INT || GET_CODE (op1) == CONST_DOUBLE)) + ret = "#"; + + else if (REG_P (op0) && ACCUM_P (REGNO (op0)) && GET_CODE (op1) == CONST_INT && INTVAL (op1) == 0) + ret = "clrac %0"; + + else + fatal_insn ("emit_move_2words:", insn); + + return ret; +} + + + +/* Emit the instructions to move 4 words. */ + +char * +emit_move_4words (op0, op1, tmp_si, tmp_hi, insn) + rtx op0; + rtx op1; + rtx tmp_si; + rtx tmp_hi; + rtx insn; +{ + char *ret = ""; + int gpr0_p = (REG_P (op0) && GPR_P (REGNO (op0))); + int gpr1_p = (REG_P (op1) && GPR_P (REGNO (op1))); + int mem0_p = (GET_CODE (op0) == MEM); + int mem1_p = (GET_CODE (op1) == MEM); + int const1_p = (GET_CODE (op1) == CONST_INT || GET_CODE (op1) == CONST_DOUBLE); + + /* Check for overlap */ + if (gpr0_p && refers_to_regno_p (REGNO (op0), REGNO (op0)+2, op1, (rtx *)0)) + { + if (TARGET_DEBUG_MOVE4) + { + fprintf (stderr, "\n%s: overlap found in emit_move_4words\n", + IDENTIFIER_POINTER (DECL_NAME (current_function_decl))); + debug_rtx (insn); + fprintf (stderr, "----------\n"); + } + + if (gpr1_p) + { + if (REGNO (op0) == REGNO (op1) + 2) + ret = "mv2w %C0,%C1\n\tmv2w %A0,%A1"; + else + ret = "mv2w %A0,%A1\n\tmv2w %C0,%C1"; + } + + else if (mem1_p) + ret = "ld2w %C0,%C1\n\tld2w %A0,%A1"; + + else + fatal_insn ("move 4 words overlap", insn); + } + + else if (gpr0_p && gpr1_p) + { + if (REGNO (op0) == REGNO (op1) + 2) + ret = "mv2w %C0,%C1\n\tmv2w %A0,%A1"; + else + ret = "mv2w %A0,%A1\n\tmv2w %C0,%C1"; + } + + else if (gpr0_p && mem1_p) + { + /* If we use post increment/post decrement, it allows the second instruction + to also be a 16-bit instruction */ + rtx addr = XEXP (op1, 0); + + if (GET_CODE (addr) == REG && REGNO (addr) != STACK_POINTER_REGNUM + && !IN_RANGE_P (REGNO (addr), REGNO (op0), REGNO (op0)+3)) + ret = "ld2w %A0,@%1+\n\tld2w %C0,@%1-"; + + else if (GET_CODE (addr) == REG && REGNO (addr) != STACK_POINTER_REGNUM + && IN_RANGE_P (REGNO (addr), REGNO (op0)+2, REGNO (op0)+3)) + ret = "ld2w %A0,@%1+\n\tld2w %C0,@%1"; + + else + ret = "ld2w %A0,%A1\n\tld2w %C0,%C1"; + } + + else if (mem0_p && gpr1_p) + { + /* If we use post increment/post decrement, it allows the second instruction + to also be a 16-bit instruction */ + rtx addr = XEXP (op0, 0); + + if (GET_CODE (addr) == REG && REGNO (addr) != STACK_POINTER_REGNUM + && !IN_RANGE_P (REGNO (addr), REGNO (op1), REGNO (op1)+3)) + ret = "st2w %A1,@%0+\n\tst2w %C1,@%0-"; + else + ret = "st2w %A1,%A0\n\tst2w %C1,%C0"; + } + + else if (mem0_p && mem1_p) + { + if (TARGET_DEBUG_MOVE4) + { + fprintf (stderr, "\n%s: memory to memory move in emit_move_4wordsn", + IDENTIFIER_POINTER (DECL_NAME (current_function_decl))); + + debug_rtx (insn); + fprintf (stderr, "----------\n"); + } + + /* Check for the scratch space overlapping registers used for pointers. */ + if (GET_CODE (tmp_si) == REG && GET_MODE (tmp_si) == SImode) + { + int reg = REGNO (tmp_si); + + if (refers_to_regno_p (reg, reg+2, op0, (rtx *)0) + || refers_to_regno_p (reg, reg+2, op1, (rtx *)0)) + { + if (!refers_to_regno_p (reg, reg+1, op0, (rtx *)0) + && !refers_to_regno_p (reg, reg+1, op1, (rtx *)0)) + { + tmp_si = NULL_RTX; + tmp_hi = gen_rtx (REG, HImode, reg); + } + else if (!refers_to_regno_p (reg+1, reg+2, op0, (rtx *)0) + && !refers_to_regno_p (reg+1, reg+2, op1, (rtx *)0)) + { + tmp_si = NULL_RTX; + tmp_hi = gen_rtx (REG, HImode, reg+1); + } + else + fatal_insn ("emit_move_4words:", insn); + } + } + else if (GET_CODE (tmp_hi) == REG && GET_MODE (tmp_hi) == HImode) + { + if (refers_to_regno_p (REGNO (tmp_hi), REGNO (tmp_hi)+1, op0, (rtx *)0) + || refers_to_regno_p (REGNO (tmp_hi), REGNO (tmp_hi)+1, op1, (rtx *)0)) + fatal_insn ("emit_move_4words:", insn); + } + + if (tmp_si && GET_CODE (tmp_si) == REG && GET_MODE (tmp_si) == SImode) + ret = "ld2w %2,%A1\n\tst2w %2,%A0\n\tld2w %2,%C1\n\tst2w %2,%C0"; + + else if (tmp_hi && GET_CODE (tmp_hi) == REG && GET_MODE (tmp_hi) == HImode) + ret = "ld %3,%A1\nst %3,%A0\nld %3,%B1\nst %3,%B0\nld %3,%C1\nst %3,%C0\nld %3,%D1\nst %3,%D0\n"; + + else + fatal_insn ("emit_move_4words:", insn); + } + + else if (gpr0_p + && (GET_CODE (op1) == CONST_INT || GET_CODE (op1) == CONST_DOUBLE)) + ret = "ldi %A0,%A1\n\tldi %B0,%B1\n\tldi %C0,%C1\n\tldi %D0,%D1"; + + else if (mem0_p + && ((GET_CODE (op1) == CONST_INT && INTVAL (op1) == 0) + || (GET_CODE (op1) == CONST_DOUBLE + && CONST_DOUBLE_LOW (op1) == 0 + && CONST_DOUBLE_HIGH (op1) == 0))) + ret = "st %.,%A0\n\tst %.,%B0\n\tst %.,%C0\n\tst %.,%D0"; + + else if (mem0_p && const1_p && GET_CODE (tmp_si) == REG && GET_MODE (tmp_si) == SImode) + { + int part0, part1, part2, part3; + + if (GET_CODE (op1) == CONST_INT && INTVAL (op1) < 0) + { + HOST_WIDE_INT value = INTVAL (op1); + part0 = 0xffff; + part1 = 0xffff; + part2 = (value >> 16) & 0xffff; + part3 = value & 0xffff; + } + + else if (GET_CODE (op1) == CONST_INT) + { + HOST_WIDE_INT value = INTVAL (op1); + part0 = 0; + part1 = 0; + part2 = (value >> 16) & 0xffff; + part3 = value & 0xffff; + } + + else + { + part0 = (CONST_DOUBLE_HIGH (op1) >> 16) & 0xffff; + part1 = CONST_DOUBLE_HIGH (op1) & 0xffff; + part2 = (CONST_DOUBLE_LOW (op1) >> 16) & 0xffff; + part3 = CONST_DOUBLE_LOW (op1) & 0xffff; + } + + if (part0 == part2 && part1 == part3) + ret = "ldi %U2,%A1\n\tldi %L2,%B1\n\tst2w %2,%A0\n\tst2w %2,%C0"; + + else if (part0 == part2) + ret = "ldi %U2,%A1\n\tldi %L2,%B1\n\tst2w %2,%A0\n\tldi %L2,%D1\n\tst2w %2,%C0"; + + else if (part1 == part3) + ret = "ldi %U2,%A1\n\tldi %L2,%B1\n\tst2w %2,%A0\n\tldi %U2,%C1\n\tst2w %2,%C0"; + + else + ret = "ldi %U2,%A1\n\tldi %L2,%B1\n\tst2w %2,%A0\n\tldi %U2,%C1\n\tldi %L2,%D1\n\tst2w %2,%C0"; + } + + else + fatal_insn ("emit_move_4words:", insn); + + return ret; +} + + + +/* Emit code to do add of multiword values */ + +char * +emit_add (operands, insn) + rtx operands[], insn; +{ + rtx op0 = operands[0]; + rtx op1 = operands[1]; + rtx op2 = operands[2]; + enum machine_mode mode = GET_MODE (op0); + int regno0 = (GET_CODE (op0) == REG) ? REGNO (op0) : -1; + int regno1 = (GET_CODE (op1) == REG) ? REGNO (op1) : -1; + int regno2 = (GET_CODE (op2) == REG) ? REGNO (op2) : -1; + HOST_WIDE_INT value; + + if (mode == SImode) + { + if (GET_CODE (op2) != CONST_INT) + { + if (GPR_P (regno0) && regno0 == regno1 && GPR_P (regno2)) + return "add2w %0,%2"; + + else if (ACCUM_P (regno0) && regno0 == regno1 && (GPR_P (regno2) || ACCUM_P (regno2))) + return "add %0,%2"; + + else if (GPR_P (regno0) && (GPR_P (regno1) || ACCUM_P (regno1)) && ACCUM_P (regno2) && TARGET_ADDAC3) + return "addac3 %0,%1,%2"; + } + + else if (regno0 != regno1) + return "ldi %U0,%U2\n\tldi %L0,%L2\n\tadd2w %0,%1"; + + else if (INTVAL (op2) < 0) + fatal_insn ("emit_add, negative constant", insn); + + else if (GPR_P (regno0) && GPR_P (regno1)) + { + value = INTVAL (op2); + if (IN_RANGE_P (value, 1, 16) && regno0 == regno1) + return "addi %L0,%2\n\tcpfg f0,c\n\texef0t || addi %U0,1"; + + else if (regno0 == regno1) + return "add3 %L0,%L0,%2\n\tcpfg f0,c\n\texef0t || addi %U0,1"; + + else + return "add3 %L0,%L1,%2\n\tcpfg f0,c || mv %U0,%U1\n\texef0t || addi %U0,1"; + } + } + + else if (mode == DImode && regno0 == regno1 && GPR_P (regno0)) + { + int num_carries = 0; + if (GPR_P (regno2)) + { + output_asm_insn ("add2w %A0,%A2", operands); + output_asm_insn ("add2w %C0,%C2", operands); + num_carries = 2; + } + + else if (GET_CODE (op2) == CONST_INT && INTVAL (op2) >= 0) + { + num_carries = 3; + value = INTVAL (op2); + if (IN_RANGE_P (value, 1, 16) && regno0 == regno1) + output_asm_insn ("addi %D0,%2", operands); + + else if (IN_RANGE_P (value, 1, 16) && TARGET_SMALL_INSNS && optimize) + output_asm_insn ("mv %D0,%D1\n\taddi %D0,%2", operands); + + else + output_asm_insn ("add3 %D0,%D1,%2", operands); + } + + else if (GET_CODE (op2) == CONST_DOUBLE + && CONST_DOUBLE_HIGH (op2) == 0 + && CONST_DOUBLE_LOW (op2) >= 0) + { + num_carries = 3; + value = CONST_DOUBLE_LOW (op2); + if (IN_RANGE_P (value, 1, 16) && regno0 == regno1) + output_asm_insn ("addi %D0,%2", operands); + + else if (IN_RANGE_P (value, 1, 16) && TARGET_SMALL_INSNS && optimize) + output_asm_insn ("mv %D0,%D1\n\taddi %D0,%2", operands); + + else + output_asm_insn ("add3 %D0,%D1,%2", operands); + } + + if (num_carries) + { + output_asm_insn ("cpfg f0,c", operands); + + if (num_carries == 3) + { + output_asm_insn ("exef0t || addi %C0,1", operands); + output_asm_insn ("cpfg f0,c || exef0t", operands); + } + + output_asm_insn ("exef0t || addi %B0,1", operands); + output_asm_insn ("cpfg f0,c || exef0t", operands); + output_asm_insn ("exef0t || addi %A0,1", operands); + return ""; + } + } + + fatal_insn ("emit_add:", insn); +} + + +/* Emit code to do subtract of multiword values */ + +char * +emit_subtract (operands, insn) + rtx operands[], insn; +{ + rtx op0 = operands[0]; + rtx op1 = operands[1]; + rtx op2 = operands[2]; + enum machine_mode mode = GET_MODE (op0); + int regno0 = (GET_CODE (op0) == REG) ? REGNO (op0) : -1; + int regno1 = (GET_CODE (op1) == REG) ? REGNO (op1) : -1; + int regno2 = (GET_CODE (op2) == REG) ? REGNO (op2) : -1; + HOST_WIDE_INT value; + + if (mode == SImode) + { + if (GPR_P (regno0) && regno0 == regno1 && GPR_P (regno2)) + return "sub2w %0,%2"; + + else if (ACCUM_P (regno0) && regno0 == regno1 && (GPR_P (regno2) || ACCUM_P (regno2))) + return "sub %0,%2"; + + else if (GPR_P (regno0) && (GPR_P (regno1) || ACCUM_P (regno1)) && ACCUM_P (regno2) && TARGET_ADDAC3) + return "subac3 %0,%1,%2"; + } + + else if (mode == DImode && GPR_P (regno0) && regno0 == regno1 && GPR_P (regno2)) + return "sub2w %A0,%A2\n\tsub2w %C0,%C2\n\tcpfg f0,c\n\texef0f || subi %B0,1\n\tcpfg f0,c || exef0f\n\texef0f || subi %A0,1"; + + fatal_insn ("emit_subtract:", insn); +} + + +/* Output a conditional move instruction + operands[0] is the destination + operands[1] is the NE test + operands[2] is f0 + operands[3] is the value to move if the test was true + operands[4] is the value to move if the test was false */ + +char * +emit_cond_move (operands, insn) + rtx operands[]; + rtx insn; +{ + char *load = (char *)0; + char *store = (char *)0; + char *move_false = (char *)0; + char *move_true = (char *)0; + rtx dest = operands[0]; + rtx xop[10]; + rtx op_true; + rtx op_false; + enum machine_mode mode = GET_MODE (dest); + char buffer[80]; + + if (GET_MODE (operands[1]) == CC_REVmode) + { + op_true = operands[4]; + op_false = operands[3]; + } + else + { + op_true = operands[3]; + op_false = operands[4]; + } + + switch (mode) + { + case QImode: + load = "ldub"; + store = "stb"; + move_false = "mvf0f %0,%1"; + move_true = "mvf0t %0,%1"; + break; + + case HImode: + load = "ld"; + store = "st"; + move_false = "mvf0f %0,%1"; + move_true = "mvf0t %0,%1"; + break; + + case SImode: + case SFmode: + load = "ld2w"; + store = "st2w"; + move_false = "exef0f || mv2w %0,%1"; + move_true = "exef0t || mv2w %0,%1"; + break; + + default: + fatal_insn ("emit_cond_move", insn); + } + + xop[0] = dest; + if (GET_CODE (dest) == REG + && GET_CODE (op_false) == CONST_INT + && !refers_to_regno_p (REGNO (dest), REGNO (dest)+1, op_true, (rtx *)0)) + { + xop[1] = op_false; + output_asm_insn ("ldi %0,%1", xop); + + xop[1] = op_true; + switch (GET_CODE (op_true)) + { + case REG: + output_asm_insn (move_true, xop); + break; + + case MEM: + sprintf (buffer, "%s %%0,%%M1 || exef0t", load); + output_asm_insn (buffer, xop); + break; + + case CONST_INT: + output_asm_insn ("exef0t || ldi %0,%1", xop); + break; + + default: + fatal_insn ("emit_cond_move", insn); + } + } + + else if (GET_CODE (dest) == REG + && GET_CODE (op_true) == CONST_INT + && !refers_to_regno_p (REGNO (dest), REGNO (dest)+1, op_false, (rtx *)0)) + { + xop[1] = op_true; + output_asm_insn ("ldi %0,%1", xop); + + xop[1] = op_false; + switch (GET_CODE (op_false)) + { + case REG: + output_asm_insn (move_false, xop); + break; + + case MEM: + sprintf (buffer, "%s %%0,%%M1 || exef0f", load); + output_asm_insn (buffer, xop); + break; + + default: + fatal_insn ("emit_cond_move", insn); + } + } + + else if (GET_CODE (dest) == REG) + { + int dest_reg = REGNO (dest); + + /* Move value into register for false condition */ + xop[1] = op_false; + switch (GET_CODE (op_false)) + { + case REG: + if (REGNO (op_false) != dest_reg) + output_asm_insn (move_false, xop); + break; + + case MEM: + sprintf (buffer, "%s %%0,%%M1 || exef0f", load); + output_asm_insn (buffer, xop); + break; + + case CONST_INT: + output_asm_insn ("exef0f || ldi %0,%1", xop); + break; + + default: + fatal_insn ("emit_cond_move", insn); + } + + /* Move value into register for true condition */ + xop[1] = op_true; + switch (GET_CODE (op_true)) + { + case REG: + if (REGNO (op_true) != dest_reg) + output_asm_insn (move_true, xop); + break; + + case MEM: + sprintf (buffer, "%s %%0,%%M1 || exef0t", load); + output_asm_insn (buffer, xop); + break; + + case CONST_INT: + output_asm_insn ("exef0t || ldi %0,%1", xop); + break; + + default: + fatal_insn ("emit_cond_move", insn); + } + } + + else if (GET_CODE (dest) == MEM + && (GET_CODE (op_false) == REG + || (GET_CODE (op_false) == CONST_INT + && INTVAL (op_false) == 0 + && GET_MODE_SIZE (mode) <= 2)) + && (GET_CODE (op_true) == REG + || (GET_CODE (op_true) == CONST_INT + && INTVAL (op_true) == 0 + && GET_MODE_SIZE (mode) <= 2))) + { + xop[1] = op_false; + sprintf (buffer, "%s %s,%%M0 || exef0f", store, (GET_CODE (op_false) == CONST_INT ? "%." : "%1")); + output_asm_insn (buffer, xop); + + xop[1] = op_true; + sprintf (buffer, "%s %s,%%M0 || exef0t", store, (GET_CODE (op_true) == CONST_INT ? "%." : "%1")); + output_asm_insn (buffer, xop); + } + + else + fatal_insn ("emit_cond_move", insn); + + return ""; +} + + +/* Split a multiword logical operation into its component parts */ + +Rtx +d10v_split_logical_op (operands, code) + rtx operands[]; + enum rtx_code code; +{ + rtx ret; + rtx op0 = operands[0]; + rtx op1 = operands[1]; + rtx op2 = operands[2]; + rtx split_op0; + rtx split_op1; + rtx split_op2; + int split_values[4]; + int i, max_words; + int off0 = 0, off1 = 0, off2 = 0; + enum machine_mode mode = GET_MODE (op0); + rtx insn; + + if (GET_CODE (op0) == SUBREG) + { + off0 = SUBREG_WORD (op0); + op0 = SUBREG_REG (op0); + } + + if (GET_CODE (op0) != REG) + goto fail; + + if (GET_CODE (op1) == SUBREG) + { + off1 = SUBREG_WORD (op1); + op1 = SUBREG_REG (op1); + } + + if (GET_CODE (op1) != REG) + goto fail; + + if (code != NOT) + { + if (GET_CODE (op2) == SUBREG) + { + off2 = SUBREG_WORD (op2); + op2 = SUBREG_REG (op2); + } + + if (GET_CODE (op2) == CONST_INT || GET_CODE (op2) == CONST_DOUBLE) + { + if (mode == DImode) + d10v_split_constant64 (op2, split_values); + + else if (mode == SImode) + d10v_split_constant32 (op2, split_values); + + else + goto fail; + } + else if (GET_CODE (op2) != REG) + goto fail; + } + else + op2 = NULL_RTX; + + max_words = GET_MODE_SIZE (mode) / UNITS_PER_WORD; + start_sequence (); + + for (i = 0; i < max_words; i++) + { + if (!reload_completed) + { + split_op0 = gen_rtx (SUBREG, HImode, op0, i+off0); + split_op1 = gen_rtx (SUBREG, HImode, op1, i+off1); + } + else + { + split_op0 = gen_rtx (REG, HImode, REGNO (op0) + i+off0); + split_op1 = gen_rtx (REG, HImode, REGNO (op1) + i+off1); + } + + if (code == NOT) + { + emit_insn (gen_rtx (SET, VOIDmode, + split_op0, + gen_rtx (NOT, HImode, split_op1))); + continue; + } + + if (GET_CODE (op2) != REG) + { + /* Optimize <reg> <op>= <value> operations to skip NOPs */ + if ((REGNO (op0) + i + off0) == (REGNO (op1) + i + off1)) + { + if (split_values[i] == 0 && code == IOR) + continue; + + if ((split_values[i] & 0xffff) == 0xffff && code == AND) + continue; + } + + /* Optimize <reg1> = <reg2> <op> <value> that turn into moves */ + else if ((split_values[i] == 0 && code == IOR) + || ((split_values[i] & 0xffff) == 0xffff && code == AND)) + { + emit_insn (gen_rtx (SET, VOIDmode, split_op0, split_op1)); + continue; + } + + split_op2 = GEN_INT (split_values[i]); + } + else + split_op2 = gen_rtx (SUBREG, HImode, op2, i+off2); + + emit_insn (gen_rtx (SET, VOIDmode, + split_op0, + gen_rtx (code, HImode, split_op1, split_op2))); + } + + ret = gen_sequence (); + end_sequence (); + return ret; + +fail: + if (code != NOT) + insn = gen_rtx (SET, VOIDmode, + operands[0], + gen_rtx (code, mode, operands[1], operands[2])); + else + insn = gen_rtx (SET, VOIDmode, + operands[0], + gen_rtx (NOT, mode, operands[1])); + + fatal_insn ("bad insn to d10v_split_logical_op", insn); + return NULL_RTX; +} + + +/* Split a 32-bit constant into 2 16-bit values. */ + +static void +d10v_split_constant32 (x, values) + rtx x; + int values[]; +{ + enum rtx_code code = GET_CODE (x); + HOST_WIDE_INT v; + + if (code == CONST_INT) + { + v = INTVAL (x); + values[0] = SIGN_EXTEND_SHORT (v >> 16); + values[1] = SIGN_EXTEND_SHORT (v); + } + + else if (code == CONST_DOUBLE && GET_MODE (x) == SFmode) + { + REAL_VALUE_TYPE rv; + + REAL_VALUE_FROM_CONST_DOUBLE (rv, x); + REAL_VALUE_TO_TARGET_SINGLE (rv, v); + values[0] = SIGN_EXTEND_SHORT (v >> 16); + values[1] = SIGN_EXTEND_SHORT (v); + } + + else + fatal_insn ("Bad rtx passed to d10v_split_constant32:", x); +} + + +/* Split a 64-bit constant into 4 16-bit values. */ + +static void +d10v_split_constant64 (x, values) + rtx x; + int values[]; +{ + enum rtx_code code = GET_CODE (x); + HOST_WIDE_INT v; + + if (code == CONST_INT) + { + v = INTVAL (x); + if (v < 0) + values[0] = values[1] = -1; + else + values[0] = values[1] = 0; + + values[2] = SIGN_EXTEND_SHORT (v >> 16); + values[3] = SIGN_EXTEND_SHORT (v); + } + + else if (code == CONST_DOUBLE && GET_MODE (x) == DFmode) + { + long t[2]; + REAL_VALUE_TYPE rv; + + /* d10v is big endian, so the high word is first */ + REAL_VALUE_FROM_CONST_DOUBLE (rv, x); + REAL_VALUE_TO_TARGET_DOUBLE (rv, t); + values[0] = SIGN_EXTEND_SHORT (t[0] >> 16); + values[1] = SIGN_EXTEND_SHORT (t[0]); + values[2] = SIGN_EXTEND_SHORT (t[1] >> 16); + values[3] = SIGN_EXTEND_SHORT (t[1]); + } + + else if (code == CONST_DOUBLE + && (GET_MODE (x) == DImode || GET_MODE (x) == VOIDmode)) + { + v = CONST_DOUBLE_HIGH (x); + values[0] = SIGN_EXTEND_SHORT (v >> 16); + values[1] = SIGN_EXTEND_SHORT (v); + + v = CONST_DOUBLE_LOW (x); + values[2] = SIGN_EXTEND_SHORT (v >> 16); + values[3] = SIGN_EXTEND_SHORT (v); + } + + else + fatal_insn ("Bad rtx passed to d10v_split_constant64:", x); +} + + +/* Print an integer constant expression in assembler syntax. + Note, for the d10v, we need to explicitly truncate integer + constants to 16 bits. */ + +void +d10v_output_addr_const (file, x) + FILE *file; + rtx x; +{ + char buf[256]; + + restart: + switch (GET_CODE (x)) + { + default: + output_addr_const (file, x); + break; + + case SYMBOL_REF: + /* We could use parentheses only for symbols whose names conflict + with the register names. But it would make the compiler + slower. */ + fputc ('(', file); + assemble_name (file, XSTR (x, 0)); + fputc (')', file); + break; + + case LABEL_REF: + ASM_GENERATE_INTERNAL_LABEL (buf, "L", CODE_LABEL_NUMBER (XEXP (x, 0))); + assemble_name (file, buf); + break; + + case CODE_LABEL: + ASM_GENERATE_INTERNAL_LABEL (buf, "L", CODE_LABEL_NUMBER (x)); + assemble_name (file, buf); + break; + + case CONST_INT: + fprintf (file, "%d", SIGN_EXTEND_SHORT (INTVAL (x))); + break; + + case CONST: + d10v_output_addr_const (file, XEXP (x, 0)); + break; + + case PLUS: + /* Some assemblers need integer constants to appear last (eg masm). */ + if (GET_CODE (XEXP (x, 0)) == CONST_INT) + { + d10v_output_addr_const (file, XEXP (x, 1)); + if (INTVAL (XEXP (x, 0)) >= 0) + fprintf (file, "+"); + d10v_output_addr_const (file, XEXP (x, 0)); + } + else + { + d10v_output_addr_const (file, XEXP (x, 0)); + if (INTVAL (XEXP (x, 1)) >= 0) + fprintf (file, "+"); + d10v_output_addr_const (file, XEXP (x, 1)); + } + break; + + case MINUS: + /* Avoid outputting things like x-x or x+5-x, + since some assemblers can't handle that. */ + x = simplify_subtraction (x); + if (GET_CODE (x) != MINUS) + goto restart; + + d10v_output_addr_const (file, XEXP (x, 0)); + fprintf (file, "-"); + if (GET_CODE (XEXP (x, 1)) == CONST_INT + && INTVAL (XEXP (x, 1)) < 0) + { + fprintf (file, ASM_OPEN_PAREN); + d10v_output_addr_const (file, XEXP (x, 1)); + fprintf (file, ASM_CLOSE_PAREN); + } + else + d10v_output_addr_const (file, XEXP (x, 1)); + break; + + case ZERO_EXTEND: + case SIGN_EXTEND: + d10v_output_addr_const (file, XEXP (x, 0)); + break; + } +} + + +/* A C compound statement to output to stdio stream STREAM the + assembler syntax for an instruction operand that is a memory + reference whose address is X. X is an RTL expression. + + On some machines, the syntax for a symbolic address depends on the + section that the address refers to. On these machines, define the + macro `ENCODE_SECTION_INFO' to store the information into the + `symbol_ref', and then check for it here. *Note Assembler + Format::. */ + +void +print_operand_address (stream, x) + FILE *stream; + rtx x; +{ + rtx x0, x1; + + if (GET_CODE (x) == MEM) + x = XEXP (x, 0); + + switch (GET_CODE (x)) + { + default: + break; + + case REG: + fputs (reg_names[ REGNO (x) ], stream); + return; + + case CONST_INT: + fprintf (stream, "%d", SIGN_EXTEND_SHORT (INTVAL (x))); + return; + + case SYMBOL_REF: + case CONST: + case LABEL_REF: + d10v_output_addr_const (stream, x); + return; + } + + fatal_insn ("Bad insn to print_operand_address:", x); +} + + +/* Print a memory reference suitable for the ld/st instructions. */ + +static void +print_operand_memory_reference (stream, x) + FILE *stream; + rtx x; +{ + rtx x0, x1; + int offset; + + switch (GET_CODE (x)) + { + default: + fatal_insn ("Bad insn to print_operand_memory_reference:", x); + break; + + case SUBREG: + offset = 0; + do { + offset += SUBREG_WORD (x); + x = SUBREG_REG (x); + } while (GET_CODE (x) == SUBREG); + fprintf (stream, "@%s", reg_names[ REGNO (x)+offset ]); + break; + + case REG: + fprintf (stream, "@%s", reg_names[ REGNO (x) ]); + break; + + case CONST_INT: + fprintf (stream, "@(%d,%s)", SIGN_EXTEND_SHORT (INTVAL (x)), reg_names[ GPR_ZERO_REGNUM ]); + break; + + case SYMBOL_REF: + case CONST: + case LABEL_REF: + fputs ("@(", stream); + d10v_output_addr_const (stream, x); + fprintf (stream, ",%s)", reg_names[ GPR_ZERO_REGNUM ]); + break; + + case PLUS: + x0 = XEXP (x, 0); + x1 = XEXP (x, 1); + offset = 0; + while (GET_CODE (x0) == SUBREG) + { + offset += SUBREG_WORD (x0); + x0 = SUBREG_REG (x0); + } + + if (CONSTANT_ADDRESS_P (x1) && GET_CODE (x0) == REG && GPR_P (REGNO (x0))) + { + if (GET_CODE (x1) == CONST_INT && INTVAL (x1) == 0) + fprintf (stream, "@%s", reg_names[ REGNO (x0)+offset ]); + else if (GET_CODE (x1) == CONST_INT) + fprintf (stream, "@(%d,%s)", SIGN_EXTEND_SHORT (INTVAL (x1)), + reg_names[ REGNO (x0)+offset ]); + else + { + fputs ("@(", stream); + d10v_output_addr_const (stream, x1); + fprintf (stream, ",%s)", reg_names[ REGNO (x0)+offset ]); + return; + } + } + else + fatal_insn ("Bad insn to print_operand_memory_reference:", x); + + break; + + case POST_INC: + x0 = XEXP (x, 0); + if (GET_CODE (x0) == REG && GPR_P (REGNO (x0))) + fprintf (stream, "@%s+", reg_names[ REGNO (x0) ]); + else + fatal_insn ("Bad insn to print_operand_memory_reference:", x); + + break; + + case POST_DEC: + x0 = XEXP (x, 0); + if (GET_CODE (x0) == REG && GPR_P (REGNO (x0)) + && REGNO (x0) != STACK_POINTER_REGNUM) + fprintf (stream, "@%s-", reg_names[ REGNO (x0) ]); + else + fatal_insn ("Bad insn to print_operand_memory_reference:", x); + + break; + + case PRE_DEC: + x0 = XEXP (x, 0); + if (GET_CODE (x0) == REG && REGNO (x0) == STACK_POINTER_REGNUM) + fprintf (stream, "@-%s", reg_names[ REGNO (x0) ]); + else + fatal_insn ("Bad insn to print_operand_memory_reference:", x); + + break; + + } +} + + +/* A C compound statement to output to stdio stream STREAM the + assembler syntax for an instruction operand X. X is an RTL + expression. + + LETTER is a value that can be used to specify one of several ways of + printing the operand. It is used when identical operands must be + printed differently depending on the context. LETTER comes from the + `%' specification that was used to request printing of the + operand. If the specification was just `%DIGIT' then LETTER is 0; + if the specification was `%LTR DIGIT' then LETTER is the ASCII code + for LTR. + + If X is a register, this macro should print the register's name. + The names can be found in an array `reg_names' whose type is `char + *[]'. `reg_names' is initialized from `REGISTER_NAMES'. + + When the machine description has a specification `%PUNCT' (a `%' + followed by a punctuation character), this macro is called with a + null pointer for X and the punctuation character for LETTER. */ + +void +print_operand (stream, x, letter) + FILE *stream; + rtx x; + int letter; +{ + enum rtx_code code = (x) ? GET_CODE (x) : NIL; + int regadd; + int split_values[4]; + int shift, log; + rtx x0; + + switch (letter) + { + case '|': /* Issue parallel instructions */ + fputs (" || ", stream); + break; + + case '.': /* Output r14 */ + fputs (reg_names[GPR_ZERO_REGNUM], stream); + break; + + case 'U': /* Extract the upper part (word 0) of a 32-bit value */ + regadd = 0; + goto common_32bit; + + case 'L': /* Extract the lower part (word 1) of a 32-bit value */ + regadd = 1; + /* fall through */ + + common_32bit: + if (code == CONST_INT || code == CONST_DOUBLE) + { + d10v_split_constant32 (x, split_values); + fprintf (stream, "%d", SIGN_EXTEND_SHORT (split_values[regadd])); + } + + else if (code == REG) + fprintf (stream, "%s", reg_names[ REGNO (x)+regadd ]); + + else if (code == MEM && offsettable_memref_p (x)) + print_operand_address (stream, plus_constant (XEXP (x, 0), regadd * UNITS_PER_WORD)); + + else + fatal_insn ("Bad insn for %U/%L:", x); + + break; + + case 'A': /* Extract word 0 of a 64-bit value */ + case 'B': /* Extract word 1 of a 64-bit value */ + case 'C': /* Extract word 2 of a 64-bit value */ + case 'D': /* Extract word 3 of a 64-bit value */ + regadd = letter - 'A'; + if (code == CONST_INT || code == CONST_DOUBLE) + { + d10v_split_constant64 (x, split_values); + fprintf (stream, "%d", split_values[regadd]); + } + + else if (code == REG) + fprintf (stream, "%s", reg_names[ REGNO (x)+regadd ]); + + else if (code == MEM && offsettable_memref_p (x)) + print_operand_memory_reference (stream, plus_constant (XEXP (x, 0), regadd * UNITS_PER_WORD)); + + else + fatal_insn ("Bad insn for %A/%B/%C/%D:", x); + + break; + + case 'M': /* Print a memory reference for ld/st instructions */ + if (GET_CODE (x) != MEM) + fatal_insn ("Bad insn to print_operand 'M' modifier:", x); + + print_operand_memory_reference (stream, XEXP (x, 0)); + break; + + case 'F': /* Print an appropriate 'f' or 't' for a false comparision instruction. */ + case 'T': /* Print an appropriate 'f' or 't' for a true comparision instruction. */ + if ((GET_CODE (x) == NE || GET_CODE (x) == EQ) + && (GET_MODE (x) == CCmode || GET_MODE (x) == CC_REVmode) + && GET_CODE (XEXP (x, 0)) == REG && REGNO (XEXP (x, 0)) == F0_REGNUM + && GET_CODE (XEXP (x, 1)) == CONST_INT && INTVAL (XEXP (x, 1)) == 0) + { + int true_false = (GET_MODE (x) == CC_REVmode); + + if (letter == 'F') + true_false = !true_false; + + if (GET_CODE (x) == EQ) + true_false = !true_false; + + putc ((true_false) ? 't' : 'f', stream); + } + + else + fatal_insn ("Bad insn to print_operand 'F' or 'T' modifier:", x); + break; + + case 'b': /* emit offset single bit to change */ + if (GET_CODE (x) == CONST_INT && (log = exact_log2 (INTVAL (x))) >= 0) + fprintf (stream, "%d", 15 - log); + + else if (GET_CODE (x) == CONST_INT && (log = exact_log2 (~ INTVAL (x))) >= 0) + fprintf (stream, "%d", 15 - log); + + else + fatal_insn ("Bad insn to print_operand 'b' modifier:", x); + break; + + case 's': /* print ldi.s value for composing ldi.l out of ldi.s and slli */ + if (GET_CODE (x) == CONST_INT + && (shift = ldi_shift_operand (x, HImode)) > 0) + { + fprintf (stream, "%d", ((int) INTVAL (x)) >> shift); + } + + else + fatal_insn ("Bad insn to print_operand 'S' modifier:", x); + break; + + case 'S': /* print shift value for composing ldi.l out of ldi.s and slli */ + if (GET_CODE (x) == CONST_INT + && (shift = ldi_shift_operand (x, HImode)) > 0) + { + fprintf (stream, "%d", shift); + } + + else + fatal_insn ("Bad insn to print_operand 'S' modifier:", x); + break; + + case 'u': /* print a 'u' if the operator is ZERO_EXTEND */ + if (GET_CODE (x) == ZERO_EXTEND) + putc ('u', stream); + break; + + case 0: + if (code == REG) + fputs (reg_names[ REGNO (x) ], stream); + + else if (code == CONST_INT) + fprintf (stream, "%d", (int)INTVAL (x)); + + else if (code == MEM) + print_operand_address (stream, XEXP (x, 0)); + + else if (CONSTANT_ADDRESS_P (x)) + print_operand_address (stream, x); + + else + fatal_insn ("Bad insn in print_operand, 0 case", x); + + return; + + default: + { + char buf[80]; + + sprintf (buf, "Invalid asm template character '%%%c'", letter); + fatal_insn (buf, x); + } + } +} + + +/* A C statement (sans semicolon) for initializing the variable CUM + for the state at the beginning of the argument list. The variable + has type `CUMULATIVE_ARGS'. The value of FNTYPE is the tree node + for the data type of the function which will receive the args, or 0 + if the args are to a compiler support library function. The value + of INDIRECT is nonzero when processing an indirect call, for + example a call through a function pointer. The value of INDIRECT + is zero for a call to an explicitly named function, a library + function call, or when `INIT_CUMULATIVE_ARGS' is used to find + arguments for the function being compiled. + + When processing a call to a compiler support library function, + LIBNAME identifies which one. It is a `symbol_ref' rtx which + contains the name of the function, as a string. LIBNAME is 0 when + an ordinary C function call is being processed. Thus, each time + this macro is called, either LIBNAME or FNTYPE is nonzero, but + never both of them at once. */ + +void +init_cumulative_args (cum, fntype, libname, indirect, incoming) + CUMULATIVE_ARGS *cum; + tree fntype; + rtx libname; + int indirect; + int incoming; +{ + cum->reg = 0; + cum->stack = 4; + + if (TARGET_DEBUG_ARG) + { + fprintf (stderr, "\ninit_cumulative_args:"); + if (indirect) + fputs (" indirect", stderr); + + 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)); + + putc ('\n', stderr); + } +} + + +/* If defined, a C expression that gives the alignment boundary, in + bits, of an argument with the specified mode and type. If it is + not defined, `PARM_BOUNDARY' is used for all arguments. */ + +int +function_arg_boundary (mode, type) + enum machine_mode mode; + tree type; +{ + int size = ((mode == BLKmode && type) + ? int_size_in_bytes (type) + : GET_MODE_SIZE (mode)); + + return ((size >= 2 * UNITS_PER_WORD) ? 2*BITS_PER_WORD : BITS_PER_WORD); +} + + +/* A C expression that controls whether a function argument is passed + in a register, and which register. + + The arguments are CUM, which summarizes all the previous + arguments; MODE, the machine mode of the argument; TYPE, the data + type of the argument as a tree node or 0 if that is not known + (which happens for C support library functions); and NAMED, which + is 1 for an ordinary argument and 0 for nameless arguments that + correspond to `...' in the called function's prototype. + + The value of the expression should either be a `reg' RTX for the + hard register in which to pass the argument, or zero to pass the + argument on the stack. + + For machines like the Vax and 68000, where normally all arguments + are pushed, zero suffices as a definition. + + The usual way to make the ANSI library `stdarg.h' work on a machine + where some arguments are usually passed in registers, is to cause + nameless arguments to be passed on the stack instead. This is done + by making `FUNCTION_ARG' return 0 whenever NAMED is 0. + + You may use the macro `MUST_PASS_IN_STACK (MODE, TYPE)' in the + definition of this macro to determine if this argument is of a + type that must be passed in the stack. If `REG_PARM_STACK_SPACE' + is not defined and `FUNCTION_ARG' returns non-zero for such an + argument, the compiler will abort. If `REG_PARM_STACK_SPACE' is + defined, the argument will be computed in the stack and then + loaded into a register. */ + +struct rtx_def * +function_arg (cum, mode, type, named) + CUMULATIVE_ARGS *cum; + enum machine_mode mode; + tree type; + int named; +{ + int align = ((cum->reg & 1) != 0 + && function_arg_boundary (mode, type) >= (2*BITS_PER_WORD)); + int size = ((mode == BLKmode && type) + ? int_size_in_bytes (type) + : GET_MODE_SIZE (mode)); + + rtx ret; + + if ((cum->reg + align) * 2 + size > (ARG_LAST - ARG_FIRST + 1) * 2) + ret = NULL_RTX; + + else + ret = gen_rtx (REG, mode, ARG_FIRST + cum->reg + align); + + if (TARGET_DEBUG_ARG) + fprintf (stderr, + "function_arg: words = %2d %2d, mode = %4s, named = %d, align = %d, size = %3d, arg = %s\n", + cum->reg, cum->stack, GET_MODE_NAME (mode), named, align, size, + (ret) ? reg_names[ REGNO (ret) ] : "memory"); + + return ret; +} + + + +/* If defined, a C expression that indicates when it is the called + function's responsibility to make a copy of arguments passed by + invisible reference. Normally, the caller makes a copy and passes + the address of the copy to the routine being called. When + FUNCTION_ARG_CALLEE_COPIES is defined and is nonzero, the caller + does not make a copy. Instead, it passes a pointer to the "live" + value. The called function must not modify this value. If it can + be determined that the value won't be modified, it need not make a + copy; otherwise a copy must be made. */ + +int +function_arg_callee_copies (cum, mode, type, named) + CUMULATIVE_ARGS *cum; + enum machine_mode mode; + tree type; + int named; +{ + return TARGET_CALLEE_COPIES != 0; +} + + +/* A C statement (sans semicolon) to update the summarizer variable + CUM to advance past an argument in the argument list. The values + MODE, TYPE and NAMED describe that argument. Once this is done, + the variable CUM is suitable for analyzing the *following* + argument with `FUNCTION_ARG', etc. + + This macro need not do anything if the argument in question was + passed on the stack. The compiler knows how to track the amount + of stack space used for arguments without any special help. */ + +void +function_arg_advance (cum, mode, type, named) + CUMULATIVE_ARGS *cum; + enum machine_mode mode; + tree type; + int named; +{ + int align = ((cum->reg & 1) != 0 + && function_arg_boundary (mode, type) >= (2*BITS_PER_WORD)); + + int size = ((mode == BLKmode && type) + ? int_size_in_bytes (type) + : GET_MODE_SIZE (mode)); + + int words = (size + (UNITS_PER_WORD - 1)) / UNITS_PER_WORD; + + if (cum->reg + align + words <= (ARG_LAST - ARG_FIRST + 1)) + cum->reg += align + words; + else +#if 0 /* ??? there should be no such alignment here, but there appears to be. + This is probably a backend bug. */ + cum->stack += words; +#else + cum->stack += align + words; +#endif + + if (TARGET_DEBUG_ARG) + fprintf (stderr, + "function_adv: words = %2d %2d, mode = %4s, named = %d, align = %d, size = %3d\n", + cum->reg, cum->stack, GET_MODE_NAME (mode), named, align, size); +} + + +/* Perform any needed actions needed for a function that is receiving a + variable number of arguments. + + CUM is as above. + + MODE and TYPE are the mode and type of the current parameter. + + PRETEND_SIZE is a variable that should be set to the amount of stack + that must be pushed by the prolog to pretend that our caller pushed + it. + + Normally, this macro will push all remaining incoming registers on the + stack and set PRETEND_SIZE to the length of the registers pushed. */ + +void +setup_incoming_varargs (cum, mode, type, pretend_size, no_rtl) + CUMULATIVE_ARGS *cum; + enum machine_mode mode; + tree type; + int *pretend_size; + int no_rtl; + +{ + if (TARGET_DEBUG_ARG) + fprintf (stderr, + "setup_vararg: words = %2d %2d, mode = %4s, no_rtl= %d\n", + cum->reg, cum->stack, GET_MODE_NAME (mode), no_rtl); +} + + +/* If defined, is a C expression that produces the machine-specific + code for a call to `__builtin_saveregs'. This code will be moved + to the very beginning of the function, before any parameter access + are made. The return value of this function should be an RTX that + contains the value to use as the return of `__builtin_saveregs'. + + The argument ARGS is a `tree_list' containing the arguments that + were passed to `__builtin_saveregs'. + + If this macro is not defined, the compiler will output an ordinary + call to the library function `__builtin_saveregs'. + + On the D10V return the start address of the area on the stack + used to hold arguments (including the 4 words pushed at the start + of the function for the arguments passed in registers. */ + +struct rtx_def * +expand_builtin_saveregs (args) + tree args; +{ + return gen_rtx (PLUS, Pmode, + virtual_incoming_args_rtx, + GEN_INT (- UNITS_PER_WORD * (ARG_LAST + 1 - ARG_FIRST))); +} + + +/* A C compound statement with a conditional `goto LABEL;' executed + if X (an RTX) is a legitimate memory address on the target machine + for a memory operand of mode MODE. + + It usually pays to define several simpler macros to serve as + subroutines for this one. Otherwise it may be too complicated to + understand. + + This macro must exist in two variants: a strict variant and a + non-strict one. The strict variant is used in the reload pass. It + must be defined so that any pseudo-register that has not been + allocated a hard register is considered a memory reference. In + contexts where some kind of register is required, a pseudo-register + with no hard register must be rejected. + + The non-strict variant is used in other passes. It must be + defined to accept all pseudo-registers in every context where some + kind of register is required. + + Compiler source files that want to use the strict variant of this + macro define the macro `REG_OK_STRICT'. You should use an `#ifdef + REG_OK_STRICT' conditional to define the strict variant in that + case and the non-strict variant otherwise. + + Subroutines to check for acceptable registers for various purposes + (one for base registers, one for index registers, and so on) are + typically among the subroutines used to define + `GO_IF_LEGITIMATE_ADDRESS'. Then only these subroutine macros + need have two variants; the higher levels of macros may be the + same whether strict or not. */ + +#define XREGNO_OK_FOR_BASE_P(REGNO, STRICT_P) \ + (GPR_P (REGNO) || \ + (!STRICT_P && ((REGNO) == ARG_POINTER_REGNUM \ + || (REGNO) >= FIRST_PSEUDO_REGISTER))) + +int +d10v_legitimate_address_p (mode, x, strict_p) + enum machine_mode mode; + rtx x; + int strict_p; +{ + rtx x0, x1; + int ret = 0; + + switch (GET_CODE (x)) + { + default: + break; + + case PLUS: + x0 = XEXP (x, 0); + x1 = XEXP (x, 1); + + while (GET_CODE (x0) == SUBREG) + x0 = SUBREG_REG (x0); + + while (GET_CODE (x1) == SUBREG) + x1 = SUBREG_REG (x1); + + if (GET_CODE (x0) == REG + && XREGNO_OK_FOR_BASE_P (REGNO (x0), strict_p) + && CONSTANT_P (x1) + && LEGITIMATE_CONSTANT_P (x1)) + ret = 1; + + break; + + case SUBREG: + do + { + x = SUBREG_REG (x); + } + while (GET_CODE (x) == SUBREG); /* fall through */ + + case REG: + ret = XREGNO_OK_FOR_BASE_P (REGNO (x), strict_p); + break; + + case CONST_INT: + ret = IN_RANGE_P (INTVAL (x), -32768, 32767); + break; + + case CONST: + case SYMBOL_REF: + case LABEL_REF: + ret = 1; + break; + + case POST_INC: + x0 = XEXP (x, 0); + if ((mode == HImode || mode == SImode || mode == SFmode) + && GET_CODE (x0) == REG && XREGNO_OK_FOR_BASE_P (REGNO (x0), strict_p)) + ret = 1; + break; + + case POST_DEC: + x0 = XEXP (x, 0); + if ((mode == HImode || mode == SImode || mode == SFmode) + && GET_CODE (x0) == REG && XREGNO_OK_FOR_BASE_P (REGNO (x0), strict_p) + && REGNO (x0) != STACK_POINTER_REGNUM) + ret = 1; + break; + + case PRE_DEC: + x0 = XEXP (x, 0); + if ((mode == HImode || mode == SImode || mode == SFmode) + && GET_CODE (x0) == REG && REGNO (x0) == STACK_POINTER_REGNUM) + ret = 1; + break; + } + + if (TARGET_DEBUG_ADDR) + { + fprintf (stderr, "\n========== GO_IF_LEGITIMATE_ADDRESS, mode = %s, result = %d, addresses are %sstrict\n", + GET_MODE_NAME (mode), ret, (strict_p) ? "" : "not "); + debug_rtx (x); + } + + return ret; +} + + + +/* A C compound statement that attempts to replace X with a valid + memory address for an operand of mode MODE. WIN will be a C + statement label elsewhere in the code; the macro definition may use + + GO_IF_LEGITIMATE_ADDRESS (MODE, X, WIN); + + to avoid further processing if the address has become legitimate. + + X will always be the result of a call to `break_out_memory_refs', + and OLDX will be the operand that was given to that function to + produce X. + + The code generated by this macro should not alter the substructure + of X. If it transforms X into a more legitimate form, it should + assign X (which will always be a C variable) a new value. + + It is not necessary for this macro to come up with a legitimate + address. The compiler has standard ways of doing so in all cases. + In fact, it is safe for this macro to do nothing. But often a + machine-dependent strategy can generate better code. */ + +struct rtx_def * +d10v_legitimize_address (x, oldx, mode, strict_p) + rtx x; + rtx oldx; + enum machine_mode mode; + int strict_p; +{ + rtx ret = NULL_RTX; + + if (TARGET_DEBUG_ADDR) + { + if (ret) + { + fprintf (stderr, "\n========== LEGITIMIZE_ADDRESS, transformed:\n"); + debug_rtx (x); + fprintf (stderr, "\ninto:\n"); + debug_rtx (ret); + } + else + { + fprintf (stderr, "\n========== LEGITIMIZE_ADDRESS, did nothing with:\n"); + debug_rtx (x); + } + } + + return ret; +} + + +/* A C statement or compound statement with a conditional `goto + LABEL;' executed if memory address X (an RTX) can have different + meanings depending on the machine mode of the memory reference it + is used for or if the address is valid for some modes but not + others. + + Autoincrement and autodecrement addresses typically have + mode-dependent effects because the amount of the increment or + decrement is the size of the operand being addressed. Some + machines have other mode-dependent addresses. Many RISC machines + have no mode-dependent addresses. + + You may assume that ADDR is a valid address for the machine. */ + +int +d10v_mode_dependent_address_p (addr) + rtx addr; +{ + switch (GET_CODE (addr)) + { + default: + break; + + case POST_INC: + case POST_DEC: + case PRE_DEC: + return 1; + } + return 0; +} + + +/* Return an RTX that refers to the nth subword of a multiword + expression. This is similar to operand_subword, but that + has a bias that SImode is a word. */ + +struct rtx_def * +d10v_subword (op, word_num, target_mode, source_mode) + rtx op; + int word_num; + enum machine_mode target_mode; + enum machine_mode source_mode; +{ + enum rtx_code code = GET_CODE (op); + enum machine_mode mode = GET_MODE (op); + int split_values[4]; + + if (!IN_RANGE_P (word_num, 0, 3)) + error ("div10_subword_mode: Internal error -- word_num (%d) should be >= 0 && <= 3", word_num); + + if (target_mode == SImode && word_num != 0 && word_num != 2) + error ("div10_subword_mode: Internal error -- word_num (%d) should be 0 or 2 for SImode", word_num); + + if (target_mode != HImode && target_mode != SImode) + error ("d10v_subword_mode: Internal error -- target_mode (%s) must be HImode or SImode", GET_MODE_NAME (target_mode)); + + if (code == REG) + { + if ((UNITS_PER_WORD * word_num) >= GET_MODE_SIZE (mode)) + error ("div10_subword: Internal error -- word_num (%d) is too large for mode %s", + word_num, GET_MODE_NAME (mode)); + + if (!reload_completed) + return gen_rtx (SUBREG, target_mode, op, word_num); + + return gen_rtx (REG, target_mode, REGNO (op) + word_num); + } + + else if (code == SUBREG) + { + if ((UNITS_PER_WORD * word_num) >= GET_MODE_SIZE (mode)) + error ("div10_subword: Internal error -- word_num (%d) is too large for mode %s", + word_num, GET_MODE_NAME (mode)); + + if (!reload_completed) + return gen_rtx (SUBREG, target_mode, op, word_num); + + do + { + word_num += SUBREG_WORD (op); + op = SUBREG_REG (op); + } + while (GET_CODE (op) == SUBREG); + + return gen_rtx (REG, target_mode, REGNO (op) + word_num); + } + + else if ((code == CONST_INT || code == CONST_DOUBLE) && GET_MODE_SIZE (source_mode) == 2) + { + if (word_num > 1) + error ("div10_subword: Internal error -- word_num (%d) is too large for 32-bit constants", word_num); + + d10v_split_constant32 (op, split_values); + return GEN_INT (SIGN_EXTEND_SHORT (split_values[word_num])); + } + + else if ((code == CONST_INT || code == CONST_DOUBLE) && GET_MODE_SIZE (source_mode) == 4) + { + d10v_split_constant32 (op, split_values); + return GEN_INT (SIGN_EXTEND_SHORT (split_values[word_num])); + } + + else if ((code == CONST_INT || code == CONST_DOUBLE) && GET_MODE_SIZE (source_mode) == 8) + { + HOST_WIDE_INT value; + + d10v_split_constant64 (op, split_values); + value = SIGN_EXTEND_SHORT (split_values[word_num]); + if (GET_MODE_SIZE (target_mode) == 4) + value = (value << 16) & (split_values[word_num+1] & 0xffff); + + return GEN_INT (value); + } + + else if (code == MEM && word_num == 0) + return op; + + else if (code == MEM) + return change_address (op, target_mode, plus_constant (XEXP (op, 0), word_num * 16)); + + else + fatal_insn ("Cannot get subword of:", op); + + return NULL_RTX; +} + + +/* Calculate the stack information for the current function. + + D10V stack frames look like: + + high | .... | + +-------------------------------+ + | Argument word #8 | + +-------------------------------+ + | Argument word #7 | + +-------------------------------+ + | Argument word #6 | + +-------------------------------+ + | Argument word #5 | + Prev sp +-------------------------------+ + | | + | Save for arguments 1..4 if | + | the func. uses stdarg/varargs | + | | + +-------------------------------+ + | | + | Save area for preserved regs | + | | + +-------------------------------+ + | | + | Local variables | + | | + +-------------------------------+ + | | + | alloca space if used | + | | + +-------------------------------+ + | | + | Space for outgoing arguments | + | | + +-------------------------------+ + | Return address if -mnew-stack | + +-------------------------------+ + | Previous sp if -mnew-stack | + low SP----> +-------------------------------+ +*/ + +d10v_stack_t * +d10v_stack_info () +{ + static d10v_stack_t info, zero_info; + d10v_stack_t *info_ptr = &info; + tree fndecl = current_function_decl; + tree fntype = TREE_TYPE (fndecl); + int varargs_p = 0; + tree cur_arg; + tree next_arg; + int saved_gprs; + int saved_accs; + int i; + + /* If we've already calculated the values and reload is complete, just return now */ + if (d10v_stack_cache) + return d10v_stack_cache; + + /* Zero all fields portably */ + info = zero_info; + + if (profile_flag) + regs_ever_live[RETURN_ADDRESS_REGNUM] = 1; + + /* Determine if this is a stdarg function */ + if (TYPE_ARG_TYPES (fntype) != 0 + && (TREE_VALUE (tree_last (TYPE_ARG_TYPES (fntype))) != void_type_node)) + varargs_p = 1; + else + { + /* Find the last argument, and see if it is __builtin_va_alist. */ + for (cur_arg = DECL_ARGUMENTS (fndecl); cur_arg != (tree)0; cur_arg = next_arg) + { + next_arg = TREE_CHAIN (cur_arg); + if (next_arg == (tree)0) + { + if (DECL_NAME (cur_arg) + && !strcmp (IDENTIFIER_POINTER (DECL_NAME (cur_arg)), "__builtin_va_alist")) + varargs_p = 1; + + break; + } + } + } + + /* Calculate which registers need to be saved & save area size */ + saved_accs = 0; + for (i = ACCUM_FIRST; i <= ACCUM_LAST; i++) + { + if (regs_ever_live[i] && !call_used_regs[i]) + { + info_ptr->save_p[i] = 1; + saved_accs++; + } + } + + /* If any accumulators are to be saved, we need a one two word register + pair to save the main portion of the accs and another register to + save the guard digits. */ + if (saved_accs) + { + regs_ever_live[SAVE_GUARD_REGNUM] = 1; + regs_ever_live[SAVE_ACC_REGNUM] = 1; + regs_ever_live[SAVE_ACC_REGNUM+1] = 1; + } + + saved_gprs = 0; + for (i = GPR_FIRST; i <= GPR_LAST; i++) + { + if (regs_ever_live[i] && (!call_used_regs[i] || i == RETURN_ADDRESS_REGNUM)) + { + info_ptr->save_p[i] = 1; + saved_gprs++; + } + } + + /* Determine various sizes */ + info_ptr->varargs_p = varargs_p; + info_ptr->varargs_size = D10V_ALIGN (((varargs_p) + ? (ARG_LAST + 1 - ARG_FIRST) * UNITS_PER_WORD + : 0), UNITS_PER_WORD); + + info_ptr->gpr_size = D10V_ALIGN (UNITS_PER_WORD * saved_gprs, UNITS_PER_WORD); + info_ptr->accum_size = D10V_ALIGN (3 * UNITS_PER_WORD * saved_accs, UNITS_PER_WORD); + info_ptr->vars_size = D10V_ALIGN (get_frame_size (), UNITS_PER_WORD); + info_ptr->parm_size = D10V_ALIGN (current_function_outgoing_args_size, UNITS_PER_WORD); + info_ptr->total_size = D10V_ALIGN (info_ptr->gpr_size + + info_ptr->accum_size + + info_ptr->vars_size + + info_ptr->parm_size + + info_ptr->varargs_size, (STACK_BOUNDARY / BITS_PER_UNIT)); + + if (reload_completed) + d10v_stack_cache = info_ptr; + + return info_ptr; +} + + +/* Internal function to print all of the information about the stack */ + +void +debug_stack_info (info) + d10v_stack_t *info; +{ + int i; + + if (!info) + info = d10v_stack_info (); + + 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, "\tvarargs_p = %d\n", info->varargs_p); + fprintf (stderr, "\tvarargs_size = %d\n", info->varargs_size); + fprintf (stderr, "\tvars_size = %d\n", info->vars_size); + fprintf (stderr, "\tparm_size = %d\n", info->parm_size); + fprintf (stderr, "\tgpr_size = %d\n", info->gpr_size); + fprintf (stderr, "\taccum_size = %d\n", info->accum_size); + fprintf (stderr, "\ttotal_size = %d\n", info->total_size); + fprintf (stderr, "\tsaved registers ="); + + for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) + if (info->save_p[i]) + fprintf (stderr, " %s", reg_names[i]); + + putc ('\n', stderr); + fflush (stderr); +} + + +/* Return non-zero if this function is known to have a null or 1 instruction epilogue. */ + +int +direct_return () +{ + if (reload_completed) + { + d10v_stack_t *info = d10v_stack_info (); + + /* If no epilogue code is needed, can use just a simple jump */ + if (info->total_size == 0) + return 1; + + /* Also allow the common case where r13 is reloaded before the return, + since we take the same space as a forward branch for the two. */ + if (info->total_size == UNITS_PER_WORD && info->save_p[RETURN_ADDRESS_REGNUM]) + return 1; + + /* Using similar logic, if just a small amount of local stack was allocated + and no registers saved, skip forward branch */ + if (info->total_size == info->vars_size + && IN_RANGE_P (info->total_size, 1, 16)) + return 1; + } + + return 0; +} + + +/* Internal function to print a memory reference. */ + +static void +print_memory_ref (stream, instruction, reg, offset, index_reg) + FILE *stream; + char *instruction; + int reg; + int offset; + int index_reg; +{ + if (offset == 0) + fprintf (stream, "\t%s %s,@%s\n", instruction, reg_names[reg], + reg_names[index_reg]); + else + fprintf (stream, "\t%s %s,@(%d,%s)\n", instruction, reg_names[reg], offset, + reg_names[index_reg]); +} + + +/* A C compound statement that outputs the assembler code for entry + to a function. The prologue is responsible for setting up the + stack frame, initializing the frame pointer register, saving + registers that must be saved, and allocating SIZE additional bytes + of storage for the local variables. SIZE is an integer. FILE is + a stdio stream to which the assembler code should be output. */ + +void +function_prologue (stream, size) + FILE *stream; + int size; +{ + int i, offset; + d10v_stack_t *info = d10v_stack_info (); + char *sp_name = reg_names[STACK_POINTER_REGNUM]; + int total_size = info->total_size; + int gpr_size = info->gpr_size; + int accum_size = info->accum_size; + + if (TARGET_DEBUG_STACK) + debug_stack_info (info); + + /* Save any variable arguments */ + if (info->varargs_p) + { + offset = 0; + for (i = ARG_LAST-1; i >= ARG_FIRST; i -= 2) + { + fprintf (stream, "\tst2w %s,@-%s\n", reg_names[i], sp_name); + offset -= 2 * UNITS_PER_WORD; + total_size -= 2 * UNITS_PER_WORD; + } + } + + /* Save the GPRs by pushing */ + for (i = GPR_FIRST; i <= GPR_LAST; i++) + { + if (info->save_p[i]) + { + if (GPR_EVEN_P (i) && info->save_p[i+1]) + { + total_size -= 2 * UNITS_PER_WORD; + fprintf (stream, "\tst2w %s,@-%s\n", reg_names[i++], sp_name); + } + else + { + total_size -= UNITS_PER_WORD; + fprintf (stream, "\tst %s,@-%s\n", reg_names[i], sp_name); + } + } + } + + /* Now save the ACs (both guard digits and 32-bit value) by moving into r12/r13 and pushing */ + for (i = ACCUM_FIRST; i <= ACCUM_LAST; i++) + { + if (info->save_p[i]) + { + total_size -= 3 * UNITS_PER_WORD; + fprintf (stream, "\tmvfacg %s,%s\n", reg_names[SAVE_GUARD_REGNUM], + reg_names[i]); + + fprintf (stream, "\tmv2wfac %s,%s\n", reg_names[SAVE_ACC_REGNUM], + reg_names[i]); + + fprintf (stream, "\tst %s,@-%s\n", reg_names[SAVE_GUARD_REGNUM], + sp_name); + + fprintf (stream, "\tst2w %s,@-%s\n", reg_names[SAVE_ACC_REGNUM], + sp_name); + } + } + + if (total_size > 0) + { + if (total_size <= 16) + fprintf (stream, "\tsubi %s,%d\n", sp_name, total_size); + + else + fprintf (stream, "\tadd3 %s,%s,%d\n", sp_name, sp_name, -total_size); + } + + /* Update frame pointer if needed */ + if (frame_pointer_needed) + fprintf (stream, "\tmv %s,%s\n", reg_names[FRAME_POINTER_REGNUM], sp_name); +} + + +/* A C compound statement that outputs the assembler code for exit + from a function. The epilogue is responsible for restoring the + saved registers and stack pointer to their values when the + function was called, and returning control to the caller. This + macro takes the same arguments as the macro `FUNCTION_PROLOGUE', + and the registers to restore are determined from `regs_ever_live' + and `CALL_USED_REGISTERS' in the same way. */ + +void +function_epilogue (stream, size) + FILE *stream; + int size; +{ + int i; + rtx insn = get_last_insn (); + char *sp_name = reg_names[STACK_POINTER_REGNUM]; + + /* If the last insn was a BARRIER, we don't have to write anything except + the trace table. */ + if (GET_CODE (insn) == NOTE) + insn = prev_nonnote_insn (insn); + + if (insn == 0 || GET_CODE (insn) != BARRIER) + { + d10v_stack_t *info = d10v_stack_info (); + int varargs_size = info->varargs_size; + int stack_size = info->total_size - varargs_size; + int gpr_size = info->gpr_size; + int accum_size = info->accum_size; + + /* If we have a varargs area, but no saved registers (ie, this is a stub) + fold the varargs area back into stack size */ + if (varargs_size && !gpr_size) + { + stack_size += varargs_size; + varargs_size = 0; + } + + /* Restore stack pointer if needed */ + if (frame_pointer_needed) + { + char *fp_name = reg_names[FRAME_POINTER_REGNUM]; + + /* We can combine restoring the stack pointer with the adjustment directly. */ + if (stack_size > 0) + { + fprintf (stream, "\tadd3 %s,%s,%d\n", sp_name, fp_name, stack_size - gpr_size - accum_size); + stack_size = gpr_size + accum_size; + } + else + fprintf (stream, "\tmv %s,%s\n", sp_name, fp_name); + } + + /* Restore stack pointer, other than the space needed to load registers w/postdecrement */ + stack_size -= gpr_size + accum_size; + if (stack_size > 0) + { + if (stack_size <= 16) + fprintf (stream, "\taddi %s,%d\n", sp_name, stack_size); + else + fprintf (stream, "\tadd3 %s,%s,%d\n", sp_name, sp_name, stack_size); + } + + /* Restore any saved accumulators. We must restore both the accumulator and the guard digits */ + for (i = ACCUM_FIRST; i <= ACCUM_LAST; i++) + { + if (info->save_p[i]) + { + fprintf (stream, "\tld2w %s,@%s+\n", reg_names[SAVE_ACC_REGNUM], + sp_name); + + fprintf (stream, "\tld %s,@%s+\n", reg_names[SAVE_GUARD_REGNUM], + sp_name); + + fprintf (stream, "\tmv2wtac %s,%s\n", reg_names[SAVE_ACC_REGNUM], + reg_names[i]); + + fprintf (stream, "\tmvtacg %s,%s\n", reg_names[SAVE_GUARD_REGNUM], + reg_names[i]); + } + } + + /* Restore the gprs by poping the registers. */ + for (i = GPR_LAST; i >= GPR_FIRST; i--) + { + if (info->save_p[i]) + { + if (GPR_ODD_P (i) && info->save_p[i-1]) + fprintf (stream, "\tld2w %s,@%s+\n", reg_names[--i], sp_name); + else + fprintf (stream, "\tld %s,@%s+\n", reg_names[i], sp_name); + } + } + + /* Finally remove varargs area */ + if (varargs_size) + fprintf (stream, "\taddi %s,%d\n", sp_name, varargs_size); + + /* Return to the user */ + fprintf (stream, "\tjmp %s\n", reg_names[RETURN_ADDRESS_REGNUM]); + } + + /* Clear out stack cache */ + d10v_stack_cache = (d10v_stack_t *)0; +} +/* END CYGNUS LOCAL -- meissner/d10v abi change */ |