/* Subroutines for insn-output.c for Tahoe. Copyright (C) 1989, 1991, 1997, 1998, 1999 Free Software Foundation, Inc. Contributed by the University of Buffalo (Devon Bowen, Dale Wiles and Kevin Zachmann. Changes for HCX by Piet van Oostrum, University of Utrecht, The Netherlands (piet@cs.ruu.nl) Speed tweaks by Michael Tiemann (tiemann@lurch.stanford.edu). 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 "function.h" #include "output.h" #include "insn-attr.h" #include "ggc.h" /* On tahoe, you have to go to memory to convert a register from sub-word to word. */ rtx tahoe_reg_conversion_loc; int extensible_operand (op, mode) rtx op; enum machine_mode mode; { if ((GET_CODE (op) == REG || (GET_CODE (op) == SUBREG && GET_CODE (SUBREG_REG (op)) == REG)) && tahoe_reg_conversion_loc == 0) { tahoe_reg_conversion_loc = assign_stack_local (SImode, GET_MODE_SIZE (SImode)); ggc_add_rtx_root (&tahoe_reg_conversion_loc, 1); } return general_operand (op, mode); } /* Most of the print_operand_address function was taken from the VAX since the modes are basically the same. I had to add a special case, though, for symbol references with offsets. */ print_operand_address (file, addr) FILE *file; register rtx addr; { register rtx reg1, reg2, breg, ireg; rtx offset; static char *reg_name[] = REGISTER_NAMES; retry: switch (GET_CODE (addr)) { case MEM: fprintf (file, "*"); addr = XEXP (addr, 0); goto retry; case REG: fprintf (file, "(%s)", reg_name [REGNO (addr)]); break; case PRE_DEC: fprintf (file, "-(%s)", reg_name [REGNO (XEXP (addr, 0))]); break; case POST_INC: fprintf (file, "(%s)+", reg_name [REGNO (XEXP (addr, 0))]); break; case PLUS: reg1 = 0, reg2 = 0; ireg = 0, breg = 0; offset = 0; if (CONSTANT_ADDRESS_P (XEXP (addr, 0)) && GET_CODE (XEXP (addr, 1)) == CONST_INT) output_addr_const (file, addr); if (CONSTANT_ADDRESS_P (XEXP (addr, 1)) && GET_CODE (XEXP (addr, 0)) == CONST_INT) output_addr_const (file, addr); if (CONSTANT_ADDRESS_P (XEXP (addr, 0)) || GET_CODE (XEXP (addr, 0)) == MEM) offset = XEXP (addr, 0), addr = XEXP (addr, 1); else if (CONSTANT_ADDRESS_P (XEXP (addr, 1)) || GET_CODE (XEXP (addr, 1)) == MEM) offset = XEXP (addr, 1), addr = XEXP (addr, 0); if (GET_CODE (addr) != PLUS) ; else if (GET_CODE (XEXP (addr, 0)) == MULT) reg1 = XEXP (addr, 0), addr = XEXP (addr, 1); else if (GET_CODE (XEXP (addr, 1)) == MULT) reg1 = XEXP (addr, 1), addr = XEXP (addr, 0); else if (GET_CODE (XEXP (addr, 0)) == REG) reg1 = XEXP (addr, 0), addr = XEXP (addr, 1); else if (GET_CODE (XEXP (addr, 1)) == REG) reg1 = XEXP (addr, 1), addr = XEXP (addr, 0); if (GET_CODE (addr) == REG || GET_CODE (addr) == MULT) { if (reg1 == 0) reg1 = addr; else reg2 = addr; addr = 0; } if (offset != 0) { if (addr != 0) abort (); addr = offset; } if (reg1 != 0 && GET_CODE (reg1) == MULT) breg = reg2, ireg = reg1; else if (reg2 != 0 && GET_CODE (reg2) == MULT) breg = reg1, ireg = reg2; else if (reg2 != 0 || GET_CODE (addr) == MEM) breg = reg2, ireg = reg1; else breg = reg1, ireg = reg2; if (addr != 0) output_address (offset); if (breg != 0) { if (GET_CODE (breg) != REG) abort (); fprintf (file, "(%s)", reg_name[REGNO (breg)]); } if (ireg != 0) { if (GET_CODE (ireg) == MULT) ireg = XEXP (ireg, 0); if (GET_CODE (ireg) != REG) abort (); fprintf (file, "[%s]", reg_name[REGNO (ireg)]); } break; default: output_addr_const (file, addr); } } /* Do a quick check and find out what the best way to do the mini-move is. Could be a push or a move..... */ static char * singlemove_string (operands) rtx *operands; { if (operands[1] == const0_rtx) return "clrl %0"; if (push_operand (operands[0], SImode)) return "pushl %1"; return "movl %1,%0"; } /* Given the rtx for an address, return true if the given register number is used in the address somewhere. */ int regisused (addr, regnum) rtx addr; int regnum; { if (GET_CODE (addr) == REG) return REGNO (addr) == regnum; else if (GET_CODE (addr) == MEM) return regisused (XEXP (addr, 0), regnum); else if (GET_CODE (addr) == MULT || GET_CODE (addr) == PLUS) return (regisused (XEXP (addr, 0), regnum) || regisused (XEXP (addr, 1), regnum)); return 0; } /* Given some rtx, traverse it and return the register used in a index. If no index is found, return 0. */ rtx index_reg (addr) rtx addr; { rtx temp; if (GET_CODE (addr) == MEM) return index_reg (XEXP (addr, 0)); else if (GET_CODE (addr) == MULT) { if (GET_CODE (XEXP (addr, 0)) == REG) return XEXP (addr, 0); else return XEXP (addr, 1); } else if (GET_CODE (addr) == PLUS) { if ((temp = index_reg (XEXP (addr, 0))) != 0) return temp; else return index_reg (XEXP (addr, 1)); } return 0; } /* Simulate the move double by generating two movl's. We need to be careful about mixing modes here. */ char * output_move_double (operands) rtx *operands; { enum { REGOP, OFFSOP, MEMOP, PUSHOP, POPOP, INDOP, CNSTOP, RNDOP } optype0, optype1; rtx latehalf[2]; rtx shftreg0 = 0, shftreg1 = 0; rtx temp0 = 0, temp1 = 0; rtx addreg0 = 0, addreg1 = 0; int dohighfirst = 0; /* First classify both operands. */ if (REG_P (operands[0])) optype0 = REGOP; else if (GET_CODE (operands[0]) == MEM && (shftreg0 = index_reg (operands[0])) != 0) optype0 = INDOP; else if (offsettable_memref_p (operands[0])) optype0 = OFFSOP; else if (GET_CODE (XEXP (operands[0], 0)) == PRE_DEC) { optype0 = PUSHOP; dohighfirst++; } else if (GET_CODE (operands[0]) == MEM) optype0 = MEMOP; else optype0 = RNDOP; if (REG_P (operands[1])) optype1 = REGOP; else if (GET_CODE (operands[1]) == MEM && (shftreg1 = index_reg (operands[1])) !+ 0) optype1 = INDOP; else if (offsettable_memref_p (operands[1])) optype1 = OFFSOP; else if (GET_CODE (XEXP (operands[1], 0)) == POST_INC) optype1 = POPOP; else if (GET_CODE (operands[1]) == MEM) optype1 = MEMOP; else if (CONSTANT_P (operands[1])) optype1 = CNSTOP; else optype1 = RNDOP; /* Set up for the high byte move for operand zero */ switch (optype0) { /* If it's a register, just use the next highest in the high address move. */ case REGOP: latehalf[0] = gen_rtx_REG (SImode, REGNO (operands[0]) + 1); break; /* For an offsettable address, use the GCC function to modify the operand to get an offset of 4 higher for the second move. */ case OFFSOP: latehalf[0] = adj_offsettable_operand (operands[0], 4); break; /* If the operand is MEMOP type, it must be a pointer to a pointer. So just remember to increase the mem location and use the same operand. */ case MEMOP: latehalf[0] = operands[0]; addreg0 = XEXP(operands[0],0); break; /* If we're dealing with a push instruction, just leave the operand alone since it auto-increments. */ case PUSHOP: latehalf[0] = operands[0]; break; /* Indexed addressing. If the address is considered offsettable, use the offset in the high part. Otherwise find what exactly is being added to the multiplication. If it's a mem reference, increment that with the high part being unchanged to cause the shift. If it's a reg, do the same. If we can't identify it, abort. Remember that the shift register was already set during identification. */ case INDOP: if (offsettable_memref_p (operands[0])) { latehalf[0] = adj_offsettable_operand (operands[0], 4); break; } latehalf[0] = operands[0]; temp0 = XEXP (XEXP (operands[0], 0), 0); if (GET_CODE(temp0) == MULT) { temp1 = temp0; temp0 = XEXP (XEXP (operands[0], 0), 1); } else { temp1 = XEXP (XEXP (operands[0], 0), 1); if (GET_CODE (temp1) != MULT) abort(); } if (GET_CODE (temp0) == MEM) addreg0 = temp0; else if (GET_CODE (temp0) == REG) addreg0 = temp0; else abort(); break; case RNDOP: default: abort(); } /* Do the same setup for operand one. */ switch (optype1) { case REGOP: latehalf[1] = gen_rtx_REG (SImode, REGNO (operands[1]) + 1); break; case OFFSOP: latehalf[1] = adj_offsettable_operand (operands[1], 4); break; case MEMOP: latehalf[1] = operands[1]; addreg1 = XEXP (operands[1], 0); break; case POPOP: latehalf[1] = operands[1]; break; case INDOP: if (offsettable_memref_p (operands[1])) { latehalf[1] = adj_offsettable_operand (operands[1], 4); break; } latehalf[1] = operands[1]; temp0 = XEXP (XEXP (operands[1], 0), 0); if (GET_CODE (temp0) == MULT) { temp1 = temp0; temp0 = XEXP (XEXP (operands[1], 0), 1); } else { temp1 = XEXP (XEXP (operands[1], 0), 1); if (GET_CODE (temp1) != MULT) abort(); } if (GET_CODE (temp0) == MEM) addreg1 = temp0; else if (GET_CODE (temp0) == REG) addreg1 = temp0; else abort(); break; case CNSTOP: if (GET_CODE (operands[1]) == CONST_DOUBLE) split_double (operands[1], &operands[1], &latehalf[1]); else if (CONSTANT_P (operands[1])) latehalf[1] = const0_rtx; else abort (); break; case RNDOP: default: abort (); } /* Double the register used for shifting in both of the operands but make sure the same register isn't doubled twice! */ if (shftreg0 != 0 && shftreg1 != 0 && rtx_equal_p (shftreg0, shftreg1)) output_asm_insn ("addl2 %0,%0", &shftreg0); else { if (shftreg0 != 0) output_asm_insn ("addl2 %0,%0", &shftreg0); if (shftreg1!= 0) output_asm_insn ("addl2 %0,%0", &shftreg1); } /* If the destination is a register and that register is needed in the source addressing mode, swap the order of the moves since we don't want this destroyed til last. If both regs are used, not much we can do, so abort. If these becomes a problem, maybe we can do it on the stack? */ if (GET_CODE (operands[0]) == REG && regisused (operands[1], REGNO (operands[0]))) { if (regisused (latehalf[1], REGNO(latehalf[0]))) ; else dohighfirst++; } /* If we're pushing, do the high address part first. */ if (dohighfirst) { if (addreg0 != 0 && addreg1 != 0 && rtx_equal_p (addreg0, addreg1)) output_asm_insn ("addl2 $4,%0", &addreg0); else { if (addreg0 != 0) output_asm_insn ("addl2 $4,%0", &addreg0); if (addreg1 != 0) output_asm_insn ("addl2 $4,%0", &addreg1); } output_asm_insn (singlemove_string (latehalf), latehalf); if (addreg0 != 0 && addreg1 != 0 && rtx_equal_p (addreg0, addreg1)) output_asm_insn ("subl2 $4,%0", &addreg0); else { if (addreg0 != 0) output_asm_insn ("subl2 $4,%0", &addreg0); if (addreg1 != 0) output_asm_insn ("subl2 $4,%0", &addreg1); } return singlemove_string (operands); } output_asm_insn (singlemove_string(operands), operands); if (addreg0 != 0 && addreg1 != 0 && rtx_equal_p (addreg0, addreg1)) output_asm_insn ("addl2 $4,%0", &addreg0); else { if (addreg0 != 0) output_asm_insn ("addl2 $4,%0", &addreg0); if (addreg1 != 0) output_asm_insn ("addl2 $4,%0", &addreg1); } output_asm_insn (singlemove_string (latehalf), latehalf); if (addreg0 != 0 && addreg1 != 0 && rtx_equal_p(addreg0, addreg1)) output_asm_insn ("subl2 $4,%0", &addreg0); else { if (addreg0 != 0) output_asm_insn ("subl2 $4,%0", &addreg0); if (addreg1 != 0) output_asm_insn ("subl2 $4,%0", &addreg1); } if (shftreg0 != 0 && shftreg1 != 0 && rtx_equal_p (shftreg0, shftreg1)) output_asm_insn ("shar $1,%0,%0", &shftreg0); else { if (shftreg0 != 0) output_asm_insn ("shar $1,%0,%0", &shftreg0); if (shftreg1 != 0) output_asm_insn ("shar $1,%0,%0", &shftreg1); } return ""; } /* This checks if a zero_extended cmp[bw] can be replaced by a sign_extended cmp[bw]. This can be done if the operand is a constant that fits in a byte/word or a memory operand. Besides that the next instruction must be an unsigned compare. Some of these tests are done by the machine description */ int tahoe_cmp_check (insn, op, max) rtx insn, op; int max; { register rtx next = NEXT_INSN (insn); if (GET_CODE (op) == CONST_INT && (INTVAL (op) < 0 || INTVAL (op) > max)) return 0; if (GET_RTX_CLASS (GET_CODE (next)) == 'i') { next = PATTERN (next); if (GET_CODE (next) == SET && SET_DEST (next) == pc_rtx && GET_CODE (SET_SRC (next)) == IF_THEN_ELSE) switch (GET_CODE (XEXP (SET_SRC (next), 0))) { case EQ: case NE: case LTU: case GTU: case LEU: case GEU: return 1; } } return 0; }