/* 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, */ 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 */ 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 = 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 = 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)) : "")); 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 */