| /* Subroutines for assembler code output on the NS32000. |
| Copyright (C) 1988, 1994, 1995, 1996 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. */ |
| |
| /* Some output-actions in ns32k.md need these. */ |
| #include <stdio.h> |
| #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" |
| |
| #ifdef OSF_OS |
| int ns32k_num_files = 0; |
| #endif |
| |
| void |
| trace (s, s1, s2) |
| char *s, *s1, *s2; |
| { |
| fprintf (stderr, s, s1, s2); |
| } |
| |
| /* Value is 1 if hard register REGNO can hold a value of machine-mode MODE. */ |
| |
| int |
| hard_regno_mode_ok (regno, mode) |
| int regno; |
| enum machine_mode mode; |
| { |
| switch (mode) |
| { |
| case QImode: |
| case HImode: |
| case PSImode: |
| case SImode: |
| case PDImode: |
| case VOIDmode: |
| case BLKmode: |
| if (regno < 8 || regno == 16 || regno == 17) |
| return 1; |
| else |
| return 0; |
| |
| case DImode: |
| if (regno < 8 && (regno & 1) == 0) |
| return 1; |
| else |
| return 0; |
| |
| case SFmode: |
| case SCmode: |
| if (TARGET_32081) |
| { |
| if (regno < 16) |
| return 1; |
| else |
| return 0; |
| } |
| else |
| { |
| if (regno < 8) |
| return 1; |
| else |
| return 0; |
| } |
| |
| case DFmode: |
| case DCmode: |
| if ((regno & 1) == 0) |
| { |
| if (TARGET_32081) |
| { |
| if (regno < 16) |
| return 1; |
| else |
| return 0; |
| } |
| else |
| { |
| if (regno < 8) |
| return 1; |
| else |
| return 0; |
| } |
| } |
| else |
| return 0; |
| } |
| |
| /* Used to abort here, but simply saying "no" handles TImode |
| much better. */ |
| return 0; |
| } |
| |
| /* ADDRESS_COST calls this. This function is not optimal |
| for the 32032 & 32332, but it probably is better than |
| the default. */ |
| |
| int |
| calc_address_cost (operand) |
| rtx operand; |
| { |
| int i; |
| int cost = 0; |
| |
| if (GET_CODE (operand) == MEM) |
| cost += 3; |
| if (GET_CODE (operand) == MULT) |
| cost += 2; |
| #if 0 |
| if (GET_CODE (operand) == REG) |
| cost += 1; /* not really, but the documentation |
| says different amount of registers |
| shouldn't return the same costs */ |
| #endif |
| switch (GET_CODE (operand)) |
| { |
| case REG: |
| case CONST: |
| case CONST_INT: |
| case CONST_DOUBLE: |
| case SYMBOL_REF: |
| case LABEL_REF: |
| case POST_DEC: |
| case PRE_DEC: |
| break; |
| case MULT: |
| case MEM: |
| case PLUS: |
| for (i = 0; i < GET_RTX_LENGTH (GET_CODE (operand)); i++) |
| { |
| cost += calc_address_cost (XEXP (operand, i)); |
| } |
| default: |
| break; |
| } |
| return cost; |
| } |
| |
| /* Return the register class of a scratch register needed to copy IN into |
| or out of a register in CLASS in MODE. If it can be done directly, |
| NO_REGS is returned. */ |
| |
| enum reg_class |
| secondary_reload_class (class, mode, in) |
| enum reg_class class; |
| enum machine_mode mode; |
| rtx in; |
| { |
| int regno = true_regnum (in); |
| |
| if (regno >= FIRST_PSEUDO_REGISTER) |
| regno = -1; |
| |
| /* We can place anything into GENERAL_REGS and can put GENERAL_REGS |
| into anything. */ |
| if (class == GENERAL_REGS || (regno >= 0 && regno < 8)) |
| return NO_REGS; |
| |
| /* Constants, memory, and FP registers can go into FP registers. */ |
| if ((regno == -1 || (regno >= 8 && regno < 16)) && (class == FLOAT_REGS)) |
| return NO_REGS; |
| |
| #if 0 /* This isn't strictly true (can't move fp to sp or vice versa), |
| so it's cleaner to use PREFERRED_RELOAD_CLASS |
| to make the right things happen. */ |
| if (regno >= 16 && class == GEN_AND_MEM_REGS) |
| return NO_REGS; |
| #endif |
| |
| /* Otherwise, we need GENERAL_REGS. */ |
| return GENERAL_REGS; |
| } |
| /* Generate the rtx that comes from an address expression in the md file */ |
| /* The expression to be build is BASE[INDEX:SCALE]. To recognize this, |
| scale must be converted from an exponent (from ASHIFT) to a |
| multiplier (for MULT). */ |
| rtx |
| gen_indexed_expr (base, index, scale) |
| rtx base, index, scale; |
| { |
| rtx addr; |
| |
| /* This generates an invalid addressing mode, if BASE is |
| fp or sp. This is handled by PRINT_OPERAND_ADDRESS. */ |
| if (GET_CODE (base) != REG && GET_CODE (base) != CONST_INT) |
| base = gen_rtx (MEM, SImode, base); |
| addr = gen_rtx (MULT, SImode, index, |
| gen_rtx (CONST_INT, VOIDmode, 1 << INTVAL (scale))); |
| addr = gen_rtx (PLUS, SImode, base, addr); |
| return addr; |
| } |
| |
| /* Return 1 if OP is a valid operand of mode MODE. This |
| predicate rejects operands which do not have a mode |
| (such as CONST_INT which are VOIDmode). */ |
| int |
| reg_or_mem_operand (op, mode) |
| register rtx op; |
| enum machine_mode mode; |
| { |
| return (GET_MODE (op) == mode |
| && (GET_CODE (op) == REG |
| || GET_CODE (op) == SUBREG |
| || GET_CODE (op) == MEM)); |
| } |
| |
| /* Split one or more DImode RTL references into pairs of SImode |
| references. The RTL can be REG, offsettable MEM, integer constant, or |
| CONST_DOUBLE. "operands" is a pointer to an array of DImode RTL to |
| split and "num" is its length. lo_half and hi_half are output arrays |
| that parallel "operands". */ |
| |
| void |
| split_di (operands, num, lo_half, hi_half) |
| rtx operands[]; |
| int num; |
| rtx lo_half[], hi_half[]; |
| { |
| while (num--) |
| { |
| if (GET_CODE (operands[num]) == REG) |
| { |
| lo_half[num] = gen_rtx (REG, SImode, REGNO (operands[num])); |
| hi_half[num] = gen_rtx (REG, SImode, REGNO (operands[num]) + 1); |
| } |
| else if (CONSTANT_P (operands[num])) |
| { |
| split_double (operands[num], &lo_half[num], &hi_half[num]); |
| } |
| else if (offsettable_memref_p (operands[num])) |
| { |
| lo_half[num] = operands[num]; |
| hi_half[num] = adj_offsettable_operand (operands[num], 4); |
| } |
| else |
| abort(); |
| } |
| } |
| |
| /* 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[1]) == CONST_INT |
| && INTVAL (operands[1]) <= 7 |
| && INTVAL (operands[1]) >= -8) |
| return "movqd %1,%0"; |
| return "movd %1,%0"; |
| } |
| |
| char * |
| output_move_double (operands) |
| rtx *operands; |
| { |
| enum anon1 { REGOP, OFFSOP, PUSHOP, CNSTOP, RNDOP } optype0, optype1; |
| rtx latehalf[2]; |
| |
| /* First classify both operands. */ |
| |
| if (REG_P (operands[0])) |
| optype0 = REGOP; |
| else if (offsettable_memref_p (operands[0])) |
| optype0 = OFFSOP; |
| else if (GET_CODE (XEXP (operands[0], 0)) == PRE_DEC) |
| optype0 = PUSHOP; |
| else |
| optype0 = RNDOP; |
| |
| if (REG_P (operands[1])) |
| optype1 = REGOP; |
| else if (CONSTANT_P (operands[1]) |
| || GET_CODE (operands[1]) == CONST_DOUBLE) |
| optype1 = CNSTOP; |
| else if (offsettable_memref_p (operands[1])) |
| optype1 = OFFSOP; |
| else if (GET_CODE (XEXP (operands[1], 0)) == PRE_DEC) |
| optype1 = PUSHOP; |
| 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 (); |
| |
| /* 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) |
| split_double (operands[1], &operands[1], &latehalf[1]); |
| else |
| latehalf[1] = operands[1]; |
| |
| /* If insn is effectively movd N(sp),tos then we will do the |
| high word first. We should use the adjusted operand 1 (which is N+4(sp)) |
| for the low word as well, to compensate for the first decrement of sp. |
| Given this, it doesn't matter which half we do "first". */ |
| if (optype0 == PUSHOP |
| && REGNO (XEXP (XEXP (operands[0], 0), 0)) == STACK_POINTER_REGNUM |
| && reg_overlap_mentioned_p (stack_pointer_rtx, operands[1])) |
| operands[1] = latehalf[1]; |
| |
| /* If one or both operands autodecrementing, |
| do the two words, high-numbered first. */ |
| else if (optype0 == PUSHOP || optype1 == PUSHOP) |
| { |
| output_asm_insn (singlemove_string (latehalf), latehalf); |
| return singlemove_string (operands); |
| } |
| |
| /* If the first move would clobber the source of the second one, |
| do them in the other order. */ |
| |
| /* Overlapping registers. */ |
| if (optype0 == REGOP && optype1 == REGOP |
| && REGNO (operands[0]) == REGNO (latehalf[1])) |
| { |
| /* Do that word. */ |
| output_asm_insn (singlemove_string (latehalf), latehalf); |
| /* Do low-numbered word. */ |
| return singlemove_string (operands); |
| } |
| /* Loading into a register which overlaps a register used in the address. */ |
| else if (optype0 == REGOP && optype1 != REGOP |
| && reg_overlap_mentioned_p (operands[0], operands[1])) |
| { |
| if (reg_mentioned_p (operands[0], XEXP (operands[1], 0)) |
| && reg_mentioned_p (latehalf[0], XEXP (operands[1], 0))) |
| { |
| /* If both halves of dest are used in the src memory address, |
| load the destination address into the low reg (operands[0]). |
| Then it works to load latehalf first. */ |
| rtx xops[2]; |
| xops[0] = XEXP (operands[1], 0); |
| xops[1] = operands[0]; |
| output_asm_insn ("addr %a0,%1", xops); |
| operands[1] = gen_rtx (MEM, DImode, operands[0]); |
| latehalf[1] = adj_offsettable_operand (operands[1], 4); |
| /* The first half has the overlap, Do the late half first. */ |
| output_asm_insn (singlemove_string (latehalf), latehalf); |
| /* Then clobber. */ |
| return singlemove_string (operands); |
| } |
| if (reg_mentioned_p (operands[0], XEXP (operands[1], 0))) |
| { |
| /* The first half has the overlap, Do the late half first. */ |
| output_asm_insn (singlemove_string (latehalf), latehalf); |
| /* Then clobber. */ |
| return singlemove_string (operands); |
| } |
| } |
| |
| /* Normal case. Do the two words, low-numbered first. */ |
| |
| output_asm_insn (singlemove_string (operands), operands); |
| |
| operands[0] = latehalf[0]; |
| operands[1] = latehalf[1]; |
| return singlemove_string (operands); |
| } |
| |
| int |
| check_reg (oper, reg) |
| rtx oper; |
| int reg; |
| { |
| register int i; |
| |
| if (oper == 0) |
| return 0; |
| switch (GET_CODE(oper)) |
| { |
| case REG: |
| return (REGNO(oper) == reg) ? 1 : 0; |
| case MEM: |
| return check_reg(XEXP(oper, 0), reg); |
| case PLUS: |
| case MULT: |
| return check_reg(XEXP(oper, 0), reg) || check_reg(XEXP(oper, 1), reg); |
| } |
| return 0; |
| } |
| |
| /* Returns 1 if OP contains a global symbol reference */ |
| |
| int |
| global_symbolic_reference_mentioned_p (op, f) |
| rtx op; |
| int f; |
| { |
| register char *fmt; |
| register int i; |
| |
| if (GET_CODE (op) == SYMBOL_REF) |
| { |
| if (! SYMBOL_REF_FLAG (op)) |
| return 1; |
| else |
| return 0; |
| } |
| else if (f && GET_CODE (op) != CONST) |
| return 0; |
| |
| fmt = GET_RTX_FORMAT (GET_CODE (op)); |
| for (i = GET_RTX_LENGTH (GET_CODE (op)) - 1; i >= 0; i--) |
| { |
| if (fmt[i] == 'E') |
| { |
| register int j; |
| |
| for (j = XVECLEN (op, i) - 1; j >= 0; j--) |
| if (global_symbolic_reference_mentioned_p (XVECEXP (op, i, j), 0)) |
| return 1; |
| } |
| else if (fmt[i] == 'e' |
| && global_symbolic_reference_mentioned_p (XEXP (op, i), 0)) |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| |
| /* PRINT_OPERAND is defined to call this function, |
| which is easier to debug than putting all the code in |
| a macro definition in ns32k.h. */ |
| |
| void |
| print_operand (file, x, code) |
| FILE *file; |
| rtx x; |
| char code; |
| { |
| if (code == '$') |
| PUT_IMMEDIATE_PREFIX (file); |
| else if (code == '?') |
| PUT_EXTERNAL_PREFIX (file); |
| else if (GET_CODE (x) == REG) |
| fprintf (file, "%s", reg_names[REGNO (x)]); |
| else if (GET_CODE (x) == MEM) |
| { |
| rtx tmp = XEXP (x, 0); |
| output_address (XEXP (x, 0)); |
| } |
| else if (GET_CODE (x) == CONST_DOUBLE && GET_MODE (x) != VOIDmode) |
| { |
| if (GET_MODE (x) == DFmode) |
| { |
| union { double d; int i[2]; } u; |
| u.i[0] = CONST_DOUBLE_LOW (x); u.i[1] = CONST_DOUBLE_HIGH (x); |
| PUT_IMMEDIATE_PREFIX(file); |
| #ifdef SEQUENT_ASM |
| /* Sequent likes it's floating point constants as integers */ |
| fprintf (file, "0Dx%08x%08x", u.i[1], u.i[0]); |
| #else |
| #ifdef ENCORE_ASM |
| fprintf (file, "0f%.20e", u.d); |
| #else |
| fprintf (file, "0d%.20e", u.d); |
| #endif |
| #endif |
| } |
| else |
| { |
| union { double d; int i[2]; } u; |
| u.i[0] = CONST_DOUBLE_LOW (x); u.i[1] = CONST_DOUBLE_HIGH (x); |
| PUT_IMMEDIATE_PREFIX (file); |
| #ifdef SEQUENT_ASM |
| /* We have no way of winning if we can't get the bits |
| for a sequent floating point number. */ |
| #if HOST_FLOAT_FORMAT != TARGET_FLOAT_FORMAT |
| abort (); |
| #endif |
| { |
| union { float f; long l; } uu; |
| uu.f = u.d; |
| fprintf (file, "0Fx%08x", uu.l); |
| } |
| #else |
| fprintf (file, "0f%.20e", u.d); |
| #endif |
| } |
| } |
| else |
| { |
| #ifdef NO_IMMEDIATE_PREFIX_IF_SYMBOLIC |
| if (GET_CODE (x) == CONST_INT) |
| #endif |
| PUT_IMMEDIATE_PREFIX (file); |
| output_addr_const (file, x); |
| } |
| } |
| |
| /* PRINT_OPERAND_ADDRESS is defined to call this function, |
| which is easier to debug than putting all the code in |
| a macro definition in ns32k.h . */ |
| |
| /* Completely rewritten to get this to work with Gas for PC532 Mach. |
| This function didn't work and I just wasn't able (nor very willing) to |
| figure out how it worked. |
| 90-11-25 Tatu Yl|nen <ylo@cs.hut.fi> */ |
| |
| print_operand_address (file, addr) |
| register FILE *file; |
| register rtx addr; |
| { |
| static char scales[] = { 'b', 'w', 'd', 0, 'q', }; |
| rtx offset, base, indexexp, tmp; |
| int scale; |
| extern int flag_pic; |
| |
| if (GET_CODE (addr) == PRE_DEC || GET_CODE (addr) == POST_DEC) |
| { |
| fprintf (file, "tos"); |
| return; |
| } |
| |
| offset = NULL; |
| base = NULL; |
| indexexp = NULL; |
| while (addr != NULL) |
| { |
| if (GET_CODE (addr) == PLUS) |
| { |
| if (GET_CODE (XEXP (addr, 0)) == PLUS) |
| { |
| tmp = XEXP (addr, 1); |
| addr = XEXP (addr, 0); |
| } |
| else |
| { |
| tmp = XEXP (addr,0); |
| addr = XEXP (addr,1); |
| } |
| } |
| else |
| { |
| tmp = addr; |
| addr = NULL; |
| } |
| switch (GET_CODE (tmp)) |
| { |
| case PLUS: |
| abort (); |
| case MEM: |
| if (base) |
| { |
| indexexp = base; |
| base = tmp; |
| } |
| else |
| base = tmp; |
| break; |
| case REG: |
| if (REGNO (tmp) < 8) |
| if (base) |
| { |
| indexexp = tmp; |
| } |
| else |
| base = tmp; |
| else |
| if (base) |
| { |
| indexexp = base; |
| base = tmp; |
| } |
| else |
| base = tmp; |
| break; |
| case MULT: |
| indexexp = tmp; |
| break; |
| case SYMBOL_REF: |
| if (flag_pic && ! CONSTANT_POOL_ADDRESS_P (tmp) |
| && ! SYMBOL_REF_FLAG (tmp)) |
| { |
| if (base) |
| { |
| if (indexexp) |
| abort (); |
| indexexp = base; |
| } |
| base = tmp; |
| break; |
| } |
| case CONST: |
| if (flag_pic && GET_CODE (tmp) == CONST) |
| { |
| rtx sym, off, tmp1; |
| tmp1 = XEXP (tmp,0); |
| if (GET_CODE (tmp1) != PLUS) |
| abort (); |
| |
| sym = XEXP (tmp1,0); |
| if (GET_CODE (sym) != SYMBOL_REF) |
| { |
| off = sym; |
| sym = XEXP (tmp1,1); |
| } |
| else |
| off = XEXP (tmp1,1); |
| if (GET_CODE (sym) == SYMBOL_REF) |
| { |
| if (GET_CODE (off) != CONST_INT) |
| abort (); |
| |
| if (CONSTANT_POOL_ADDRESS_P (sym) |
| || SYMBOL_REF_FLAG (sym)) |
| { |
| SYMBOL_REF_FLAG (tmp) = 1; |
| } |
| else |
| { |
| if (base) |
| { |
| if (indexexp) |
| abort (); |
| |
| indexexp = base; |
| } |
| |
| if (offset != 0) |
| abort (); |
| |
| base = sym; |
| offset = off; |
| break; |
| } |
| } |
| } |
| case CONST_INT: |
| case LABEL_REF: |
| if (offset) |
| offset = gen_rtx (PLUS, SImode, tmp, offset); |
| else |
| offset = tmp; |
| break; |
| default: |
| abort (); |
| } |
| } |
| if (! offset) |
| offset = const0_rtx; |
| |
| if (base |
| #ifndef INDEX_RATHER_THAN_BASE |
| && (flag_pic || TARGET_HIMEM) |
| && GET_CODE (base) != SYMBOL_REF |
| && GET_CODE (offset) != CONST_INT |
| #else |
| /* This is a re-implementation of the SEQUENT_ADDRESS_BUG fix. */ |
| #endif |
| && !indexexp && GET_CODE (base) == REG |
| && REG_OK_FOR_INDEX_P (base)) |
| { |
| indexexp = base; |
| base = NULL; |
| } |
| |
| /* now, offset, base and indexexp are set */ |
| #ifndef BASE_REG_NEEDED |
| if (! base) |
| { |
| #if defined (PC_RELATIVE) || defined (NO_ABSOLUTE_PREFIX_IF_SYMBOLIC) |
| if (GET_CODE (offset) == CONST_INT) |
| #endif |
| PUT_ABSOLUTE_PREFIX (file); |
| } |
| #endif |
| |
| output_addr_const (file, offset); |
| if (base) /* base can be (REG ...) or (MEM ...) */ |
| switch (GET_CODE (base)) |
| { |
| /* now we must output base. Possible alternatives are: |
| (rN) (REG ...) |
| (sp) (REG ...) |
| (fp) (REG ...) |
| (pc) (REG ...) used for SYMBOL_REF and LABEL_REF, output |
| (disp(fp)) (MEM ...) just before possible [rX:y] |
| (disp(sp)) (MEM ...) |
| (disp(sb)) (MEM ...) |
| */ |
| case REG: |
| fprintf (file, "(%s)", reg_names[REGNO (base)]); |
| break; |
| case SYMBOL_REF: |
| if (! flag_pic) |
| abort (); |
| |
| fprintf (file, "("); |
| output_addr_const (file, base); |
| fprintf (file, "(sb))"); |
| break; |
| case MEM: |
| addr = XEXP(base,0); |
| base = NULL; |
| offset = NULL; |
| while (addr != NULL) |
| { |
| if (GET_CODE (addr) == PLUS) |
| { |
| if (GET_CODE (XEXP (addr, 0)) == PLUS) |
| { |
| tmp = XEXP (addr, 1); |
| addr = XEXP (addr, 0); |
| } |
| else |
| { |
| tmp = XEXP (addr, 0); |
| addr = XEXP (addr, 1); |
| } |
| } |
| else |
| { |
| tmp = addr; |
| addr = NULL; |
| } |
| switch (GET_CODE (tmp)) |
| { |
| case REG: |
| base = tmp; |
| break; |
| case CONST: |
| case CONST_INT: |
| case SYMBOL_REF: |
| case LABEL_REF: |
| if (offset) |
| offset = gen_rtx (PLUS, SImode, tmp, offset); |
| else |
| offset = tmp; |
| break; |
| default: |
| abort (); |
| } |
| } |
| if (! offset) |
| offset = const0_rtx; |
| fprintf (file, "("); |
| output_addr_const (file, offset); |
| if (base) |
| fprintf (file, "(%s)", reg_names[REGNO (base)]); |
| else if (TARGET_SB) |
| fprintf (file, "(sb)"); |
| else |
| abort (); |
| fprintf (file, ")"); |
| break; |
| default: |
| abort (); |
| } |
| #ifdef PC_RELATIVE |
| else if (GET_CODE (offset) != CONST_INT) |
| fprintf (file, "(pc)"); |
| #ifdef BASE_REG_NEEDED |
| else if (TARGET_SB) |
| fprintf (file, "(sb)"); |
| else |
| abort (); |
| #endif |
| #endif /* PC_RELATIVE */ |
| |
| /* now print index if we have one */ |
| if (indexexp) |
| { |
| if (GET_CODE (indexexp) == MULT) |
| { |
| scale = INTVAL (XEXP (indexexp, 1)) >> 1; |
| indexexp = XEXP (indexexp, 0); |
| } |
| else |
| scale = 0; |
| if (GET_CODE (indexexp) != REG || REGNO (indexexp) >= 8) |
| abort (); |
| |
| #ifdef UTEK_ASM |
| fprintf (file, "[%c`%s]", |
| scales[scale], |
| reg_names[REGNO (indexexp)]); |
| #else |
| fprintf (file, "[%s:%c]", |
| reg_names[REGNO (indexexp)], |
| scales[scale]); |
| #endif |
| } |
| } |
| |
| /* National 32032 shifting is so bad that we can get |
| better performance in many common cases by using other |
| techniques. */ |
| char * |
| output_shift_insn (operands) |
| rtx *operands; |
| { |
| if (GET_CODE (operands[2]) == CONST_INT |
| && INTVAL (operands[2]) > 0 |
| && INTVAL (operands[2]) <= 3) |
| if (GET_CODE (operands[0]) == REG) |
| { |
| if (GET_CODE (operands[1]) == REG) |
| { |
| if (REGNO (operands[0]) == REGNO (operands[1])) |
| { |
| if (operands[2] == const1_rtx) |
| return "addd %0,%0"; |
| else if (INTVAL (operands[2]) == 2) |
| return "addd %0,%0\n\taddd %0,%0"; |
| } |
| if (operands[2] == const1_rtx) |
| return "movd %1,%0\n\taddd %0,%0"; |
| |
| operands[1] = gen_indexed_expr (const0_rtx, operands[1], operands[2]); |
| return "addr %a1,%0"; |
| } |
| if (operands[2] == const1_rtx) |
| return "movd %1,%0\n\taddd %0,%0"; |
| } |
| else if (GET_CODE (operands[1]) == REG) |
| { |
| operands[1] = gen_indexed_expr (const0_rtx, operands[1], operands[2]); |
| return "addr %a1,%0"; |
| } |
| else if (INTVAL (operands[2]) == 1 |
| && GET_CODE (operands[1]) == MEM |
| && rtx_equal_p (operands [0], operands[1])) |
| { |
| rtx temp = XEXP (operands[1], 0); |
| |
| if (GET_CODE (temp) == REG |
| || (GET_CODE (temp) == PLUS |
| && GET_CODE (XEXP (temp, 0)) == REG |
| && GET_CODE (XEXP (temp, 1)) == CONST_INT)) |
| return "addd %0,%0"; |
| } |
| else return "ashd %2,%0"; |
| return "ashd %2,%0"; |
| } |
| |
| char * |
| output_move_dconst (n, s) |
| int n; |
| char *s; |
| { |
| static char r[32]; |
| |
| if (n > -9 && n < 8) |
| strcpy (r, "movqd "); |
| else if (n > 0 && n < 256) |
| strcpy (r, "movzbd "); |
| else if (n > 0 && n < 65536) |
| strcpy (r, "movzwd "); |
| else if (n < 0 && n > -129) |
| strcpy (r, "movxbd "); |
| else if (n < 0 && n > -32769) |
| strcpy (r, "movxwd "); |
| else |
| strcpy (r, "movd "); |
| strcat (r, s); |
| return r; |
| } |