| /* Subroutines for insn-output.c for SPUR. Adapted from routines for |
| the Motorola 68000 family. |
| Copyright (C) 1988, 1991 Free Software Foundation, Inc. |
| |
| 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 "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" |
| |
| static rtx find_addr_reg (); |
| |
| char * |
| output_compare (operands, opcode, exchange_opcode, |
| neg_opcode, neg_exchange_opcode) |
| rtx *operands; |
| char *opcode; |
| char *exchange_opcode; |
| char *neg_opcode; |
| char *neg_exchange_opcode; |
| { |
| static char buf[100]; |
| operands[2] = operands[0]; |
| if (GET_CODE (cc_prev_status.value1) == CONST_INT) |
| { |
| operands[1] = cc_prev_status.value1; |
| operands[0] = cc_prev_status.value2; |
| opcode = exchange_opcode, neg_opcode = neg_exchange_opcode; |
| } |
| else |
| { |
| operands[0] = cc_prev_status.value1; |
| operands[1] = cc_prev_status.value2; |
| } |
| if (TARGET_LONG_JUMPS) |
| sprintf (buf, |
| "cmp_br_delayed %s,%%0,%%1,1f\n\tnop\n\tjump %%l2\n\tnop\n1:", |
| neg_opcode); |
| else |
| sprintf (buf, "cmp_br_delayed %s,%%0,%%1,%%l2\n\tnop", opcode); |
| return buf; |
| } |
| |
| /* Return the best assembler insn template |
| for moving operands[1] into operands[0] as a fullword. */ |
| |
| static char * |
| singlemove_string (operands) |
| rtx *operands; |
| { |
| if (GET_CODE (operands[0]) == MEM) |
| return "st_32 %r1,%0"; |
| if (GET_CODE (operands[1]) == MEM) |
| return "ld_32 %0,%1\n\tnop"; |
| if (GET_CODE (operands[1]) == REG) |
| return "add_nt %0,%1,$0"; |
| return "add_nt %0,r0,%1"; |
| } |
| |
| /* Output assembler code to perform a doubleword move insn |
| with operands OPERANDS. */ |
| |
| char * |
| output_move_double (operands) |
| rtx *operands; |
| { |
| enum { REGOP, OFFSOP, MEMOP, PUSHOP, POPOP, CNSTOP, RNDOP } optype0, optype1; |
| rtx latehalf[2]; |
| rtx addreg0 = 0, addreg1 = 0; |
| |
| /* First classify both operands. */ |
| |
| if (REG_P (operands[0])) |
| optype0 = REGOP; |
| else if (offsettable_memref_p (operands[0])) |
| optype0 = OFFSOP; |
| else if (GET_CODE (operands[0]) == MEM) |
| optype0 = MEMOP; |
| else |
| optype0 = RNDOP; |
| |
| if (REG_P (operands[1])) |
| optype1 = REGOP; |
| else if (CONSTANT_P (operands[1])) |
| optype1 = CNSTOP; |
| else if (offsettable_memref_p (operands[1])) |
| optype1 = OFFSOP; |
| else if (GET_CODE (operands[1]) == MEM) |
| optype1 = MEMOP; |
| else |
| optype1 = RNDOP; |
| |
| /* Check for the cases that the operand constraints are not |
| supposed to allow to happen. Abort if we get one, |
| because generating code for these cases is painful. */ |
| |
| if (optype0 == RNDOP || optype1 == RNDOP) |
| abort (); |
| |
| /* If an operand is an unoffsettable memory ref, find a register |
| we can increment temporarily to make it refer to the second word. */ |
| |
| if (optype0 == MEMOP) |
| addreg0 = find_addr_reg (XEXP (operands[0], 0)); |
| |
| if (optype1 == MEMOP) |
| addreg1 = find_addr_reg (XEXP (operands[1], 0)); |
| |
| /* Ok, we can do one word at a time. |
| Normally we do the low-numbered word first, |
| but if either operand is autodecrementing then we |
| do the high-numbered word first. |
| |
| In either case, set up in LATEHALF the operands to use |
| for the high-numbered word and in some cases alter the |
| operands in OPERANDS to be suitable for the low-numbered word. */ |
| |
| if (optype0 == REGOP) |
| latehalf[0] = gen_rtx (REG, SImode, REGNO (operands[0]) + 1); |
| else if (optype0 == OFFSOP) |
| latehalf[0] = adj_offsettable_operand (operands[0], 4); |
| else |
| latehalf[0] = operands[0]; |
| |
| if (optype1 == REGOP) |
| latehalf[1] = gen_rtx (REG, SImode, REGNO (operands[1]) + 1); |
| else if (optype1 == OFFSOP) |
| latehalf[1] = adj_offsettable_operand (operands[1], 4); |
| else if (optype1 == CNSTOP) |
| { |
| if (GET_CODE (operands[1]) == CONST_DOUBLE) |
| { |
| latehalf[1] = gen_rtx (CONST_INT, VOIDmode, |
| CONST_DOUBLE_HIGH (operands[1])); |
| operands[1] = gen_rtx (CONST_INT, VOIDmode, |
| CONST_DOUBLE_LOW (operands[1])); |
| } |
| else if (CONSTANT_P (operands[1])) |
| latehalf[1] = const0_rtx; |
| } |
| else |
| latehalf[1] = operands[1]; |
| |
| /* If the first move would clobber the source of the second one, |
| do them in the other order. This happens only for registers; |
| such overlap can't happen in memory unless the user explicitly |
| sets it up, and that is an undefined circumstance. */ |
| |
| if (optype0 == REGOP && optype1 == REGOP |
| && REGNO (operands[0]) == REGNO (latehalf[1])) |
| { |
| /* Make any unoffsettable addresses point at high-numbered word. */ |
| if (addreg0) |
| output_asm_insn ("add_nt %0,%0,$4", &addreg0); |
| if (addreg1) |
| output_asm_insn ("add_nt %0,%0,$4", &addreg1); |
| |
| /* Do that word. */ |
| output_asm_insn (singlemove_string (latehalf), latehalf); |
| |
| /* Undo the adds we just did. */ |
| if (addreg0) |
| output_asm_insn ("add_nt %0,%0,$-4", &addreg0); |
| if (addreg1) |
| output_asm_insn ("add_nt %0,%0,$-4", &addreg0); |
| |
| /* Do low-numbered word. */ |
| return singlemove_string (operands); |
| } |
| |
| /* Normal case: do the two words, low-numbered first. */ |
| |
| output_asm_insn (singlemove_string (operands), operands); |
| |
| /* Make any unoffsettable addresses point at high-numbered word. */ |
| if (addreg0) |
| output_asm_insn ("add_nt %0,%0,$4", &addreg0); |
| if (addreg1) |
| output_asm_insn ("add_nt %0,%0,$4", &addreg1); |
| |
| /* Do that word. */ |
| output_asm_insn (singlemove_string (latehalf), latehalf); |
| |
| /* Undo the adds we just did. */ |
| if (addreg0) |
| output_asm_insn ("add_nt %0,%0,$-4", &addreg0); |
| if (addreg1) |
| output_asm_insn ("add_nt %0,%0,$-4", &addreg1); |
| |
| return ""; |
| } |
| |
| static char * |
| output_fp_move_double (operands) |
| rtx *operands; |
| { |
| if (FP_REG_P (operands[0])) |
| { |
| if (FP_REG_P (operands[1])) |
| return "fmov %0,%1"; |
| if (GET_CODE (operands[1]) == REG) |
| { |
| rtx xoperands[2]; |
| int offset = - get_frame_size () - 8; |
| xoperands[1] = gen_rtx (REG, SImode, REGNO (operands[1]) + 1); |
| xoperands[0] = gen_rtx (CONST_INT, VOIDmode, offset + 4); |
| output_asm_insn ("st_32 %1,r25,%0", xoperands); |
| xoperands[1] = operands[1]; |
| xoperands[0] = gen_rtx (CONST_INT, VOIDmode, offset); |
| output_asm_insn ("st_32 %1,r25,%0", xoperands); |
| xoperands[1] = operands[0]; |
| output_asm_insn ("ld_dbl %1,r25,%0\n\tnop", xoperands); |
| return ""; |
| } |
| return "ld_dbl %0,%1\n\tnop"; |
| } |
| else if (FP_REG_P (operands[1])) |
| { |
| if (GET_CODE (operands[0]) == REG) |
| { |
| rtx xoperands[2]; |
| int offset = - get_frame_size () - 8; |
| xoperands[0] = gen_rtx (CONST_INT, VOIDmode, offset); |
| xoperands[1] = operands[1]; |
| output_asm_insn ("st_dbl %1,r25,%0", xoperands); |
| xoperands[1] = operands[0]; |
| output_asm_insn ("ld_32 %1,r25,%0\n\tnop", xoperands); |
| xoperands[1] = gen_rtx (REG, SImode, REGNO (operands[0]) + 1); |
| xoperands[0] = gen_rtx (CONST_INT, VOIDmode, offset + 4); |
| output_asm_insn ("ld_32 %1,r25,%0\n\tnop", xoperands); |
| return ""; |
| } |
| return "st_dbl %1,%0"; |
| } |
| } |
| |
| /* Return a REG that occurs in ADDR with coefficient 1. |
| ADDR can be effectively incremented by incrementing REG. */ |
| |
| static rtx |
| find_addr_reg (addr) |
| rtx addr; |
| { |
| while (GET_CODE (addr) == PLUS) |
| { |
| if (GET_CODE (XEXP (addr, 0)) == REG) |
| addr = XEXP (addr, 0); |
| else if (GET_CODE (XEXP (addr, 1)) == REG) |
| addr = XEXP (addr, 1); |
| else if (CONSTANT_P (XEXP (addr, 0))) |
| addr = XEXP (addr, 1); |
| else if (CONSTANT_P (XEXP (addr, 1))) |
| addr = XEXP (addr, 0); |
| else |
| abort (); |
| } |
| if (GET_CODE (addr) == REG) |
| return addr; |
| abort (); |
| } |
| |
| /* Generate code to add a large integer constant to register, reg, storing |
| * the result in a register, target. Offset must be 27-bit signed quantity */ |
| |
| static char * |
| output_add_large_offset (target, reg, offset) |
| rtx target, reg; |
| int offset; |
| { |
| rtx operands[3]; |
| int high, n, i; |
| operands[0] = target, operands[1] = reg; |
| |
| for (high = offset, n = 0; |
| (unsigned) (high + 0x2000) >= 0x4000; |
| high >>= 1, n += 1) |
| ; |
| operands[2] = gen_rtx (CONST_INT, VOIDmode, high); |
| output_asm_insn ("add_nt r2,r0,%2", operands); |
| i = n; |
| while (i >= 3) |
| output_asm_insn ("sll r2,r2,$3", operands), i -= 3; |
| if (i == 2) |
| output_asm_insn ("sll r2,r2,$2", operands); |
| else if (i == 1) |
| output_asm_insn ("sll r2,r2,$1", operands); |
| output_asm_insn ("add_nt %0,r2,%1", operands); |
| if (offset - (high << n) != 0) |
| { |
| operands[2] = gen_rtx (CONST_INT, VOIDmode, offset - (high << n)); |
| output_asm_insn ("add_nt %0,%0,%2", operands); |
| } |
| return ""; |
| } |
| |
| /* Additional TESTFN for matching. Like immediate_operand, but matches big |
| * constants */ |
| |
| int |
| big_immediate_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| return (GET_CODE (op) == CONST_INT); |
| } |