home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Education Sampler 1992 [NeXTSTEP]
/
Education_1992_Sampler.iso
/
NeXT
/
GnuSource
/
cc-61.0.1
/
cc
/
config
/
out-i860.c
< prev
next >
Wrap
C/C++ Source or Header
|
1991-06-03
|
40KB
|
1,518 lines
/* Subroutines for insn-output.c for Intel 860
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
Derived from out-sparc.c.
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, 675 Mass Ave, Cambridge, MA 02139, 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"
/* Global variables for machine-dependend things. */
/* This should go away if we pass floats to regs via
the stack instead of the frame, and if we learn how
to renumber all the registers when we don't do a save (hard!). */
extern int frame_pointer_needed;
static rtx find_addr_reg ();
/* Return non-zero only if OP is a register of mode MODE,
or const0_rtx. */
int
reg_or_0_operand (op, mode)
rtx op;
enum machine_mode mode;
{
return (op == const0_rtx || register_operand (op, mode)
|| op == CONST0_RTX (mode));
}
/* Return non-zero if this pattern, can be evaluated safely, even if it
was not asked for. */
int
safe_insn_src_p (op, mode)
rtx op;
enum machine_mode mode;
{
/* Just experimenting. */
/* No floating point src is safe if it contains an arithmetic
operation, since that operation may trap. */
switch (GET_CODE (op))
{
case CONST_INT:
case LABEL_REF:
case SYMBOL_REF:
case CONST:
return 1;
case REG:
return 1;
case MEM:
return CONSTANT_ADDRESS_P (XEXP (op, 0));
/* We never need to negate or complement constants. */
case NEG:
return (mode != SFmode && mode != DFmode);
case NOT:
case ZERO_EXTEND:
return 1;
case EQ:
case NE:
case LT:
case GT:
case LE:
case GE:
case LTU:
case GTU:
case LEU:
case GEU:
case MINUS:
case PLUS:
return (mode != SFmode && mode != DFmode);
case AND:
case IOR:
case XOR:
case LSHIFT:
case ASHIFT:
case ASHIFTRT:
case LSHIFTRT:
if ((GET_CODE (XEXP (op, 0)) == CONST_INT && ! SMALL_INT (XEXP (op, 0)))
|| (GET_CODE (XEXP (op, 1)) == CONST_INT && ! SMALL_INT (XEXP (op, 1))))
return 0;
return 1;
default:
return 0;
}
}
/* Return 1 if REG is clobbered in IN.
Return 2 if REG is used in IN.
Return 3 if REG is both used and clobbered in IN.
Return 0 if neither. */
static int
reg_clobbered_p (reg, in)
rtx reg;
rtx in;
{
register enum rtx_code code;
if (in == 0)
return 0;
code = GET_CODE (in);
if (code == SET || code == CLOBBER)
{
rtx dest = SET_DEST (in);
int set = 0;
int used = 0;
while (GET_CODE (dest) == STRICT_LOW_PART
|| GET_CODE (dest) == SUBREG
|| GET_CODE (dest) == SIGN_EXTRACT
|| GET_CODE (dest) == ZERO_EXTRACT)
dest = XEXP (dest, 0);
if (dest == reg)
set = 1;
else if (GET_CODE (dest) == REG
&& refers_to_regno_p (REGNO (reg),
REGNO (reg) + HARD_REGNO_NREGS (reg, GET_MODE (reg)),
SET_DEST (in), 0))
{
set = 1;
/* Anything that sets just part of the register
is considered using as well as setting it.
But note that a straight SUBREG of a single-word value
clobbers the entire value. */
if (dest != SET_DEST (in)
&& ! (GET_CODE (SET_DEST (in)) == SUBREG
|| UNITS_PER_WORD >= GET_MODE_SIZE (GET_MODE (dest))))
used = 1;
}
if (code == SET)
{
if (set)
used = refers_to_regno_p (REGNO (reg),
REGNO (reg) + HARD_REGNO_NREGS (reg, GET_MODE (reg)),
SET_SRC (in), 0);
else
used = refers_to_regno_p (REGNO (reg),
REGNO (reg) + HARD_REGNO_NREGS (reg, GET_MODE (reg)),
in, 0);
}
return set + used * 2;
}
if (refers_to_regno_p (REGNO (reg),
REGNO (reg) + HARD_REGNO_NREGS (reg, GET_MODE (reg)),
in, 0))
return 2;
return 0;
}
/* Return non-zero if OP can be written to without screwing up
GCC's model of what's going on. It is assumed that this operand
appears in the dest position of a SET insn in a conditional
branch's delay slot. AFTER is the label to start looking from. */
int
operand_clobbered_before_used_after (op, after)
rtx op;
rtx after;
{
extern char call_used_regs[];
/* Just experimenting. */
if (GET_CODE (op) == CC0)
return 1;
if (GET_CODE (op) == REG)
{
rtx insn;
if (op == stack_pointer_rtx)
return 0;
/* Scan forward from the label, to see if the value of OP
is clobbered before the first use. */
for (insn = NEXT_INSN (after); insn; insn = NEXT_INSN (insn))
{
if (GET_CODE (insn) == NOTE)
continue;
if (GET_CODE (insn) == INSN
|| GET_CODE (insn) == JUMP_INSN
|| GET_CODE (insn) == CALL_INSN)
{
switch (reg_clobbered_p (op, PATTERN (insn)))
{
default:
return 0;
case 1:
return 1;
case 0:
break;
}
}
/* If we reach another label without clobbering OP,
then we cannot safely write it here. */
else if (GET_CODE (insn) == CODE_LABEL)
return 0;
if (GET_CODE (insn) == JUMP_INSN)
{
if (condjump_p (insn))
return 0;
/* This is a jump insn which has already
been mangled. We can't tell what it does. */
if (GET_CODE (PATTERN (insn)) == PARALLEL)
return 0;
if (! JUMP_LABEL (insn))
return 0;
/* Keep following jumps. */
insn = JUMP_LABEL (insn);
}
}
return 1;
}
/* In both of these cases, the first insn executed
for this op will be a orh whatever%h,r0,r31,
which is tolerable. */
if (GET_CODE (op) == MEM)
return (CONSTANT_ADDRESS_P (XEXP (op, 0)));
return 0;
}
/* Return non-zero if this pattern, as a source to a "SET",
is known to yield an instruction of unit size. */
int
single_insn_src_p (op, mode)
rtx op;
enum machine_mode mode;
{
switch (GET_CODE (op))
{
case CONST_INT:
/* This is not always a single insn src, technically,
but output_delayed_branch knows how to deal with it. */
return 1;
case SYMBOL_REF:
case CONST:
/* This is not a single insn src, technically,
but output_delayed_branch knows how to deal with it. */
return 1;
case REG:
return 1;
case MEM:
return 1;
/* We never need to negate or complement constants. */
case NEG:
return (mode != DFmode);
case NOT:
case ZERO_EXTEND:
return 1;
case PLUS:
case MINUS:
/* Detect cases that require multiple instructions. */
if (CONSTANT_P (XEXP (op, 1))
&& !(GET_CODE (XEXP (op, 1)) == CONST_INT
&& SMALL_INT (XEXP (op, 1))))
return 0;
case EQ:
case NE:
case LT:
case GT:
case LE:
case GE:
case LTU:
case GTU:
case LEU:
case GEU:
/* Not doing floating point, since they probably
take longer than the branch slot they might fill. */
return (mode != SFmode && mode != DFmode);
case AND:
if (GET_CODE (XEXP (op, 1)) == NOT)
{
rtx arg = XEXP (XEXP (op, 1), 0);
if (CONSTANT_P (arg)
&& !(GET_CODE (arg) == CONST_INT
&& (SMALL_INT (arg)
|| INTVAL (arg) & 0xffff == 0)))
return 0;
}
case IOR:
case XOR:
/* Both small and round numbers take one instruction;
others take two. */
if (CONSTANT_P (XEXP (op, 1))
&& !(GET_CODE (XEXP (op, 1)) == CONST_INT
&& (SMALL_INT (XEXP (op, 1))
|| INTVAL (XEXP (op, 1)) & 0xffff == 0)))
return 0;
case LSHIFT:
case ASHIFT:
case ASHIFTRT:
case LSHIFTRT:
return 1;
case SUBREG:
if (SUBREG_WORD (op) != 0)
return 0;
return single_insn_src_p (SUBREG_REG (op), mode);
/* Not doing floating point, since they probably
take longer than the branch slot they might fill. */
case FLOAT_EXTEND:
case FLOAT_TRUNCATE:
case FLOAT:
case FIX:
case UNSIGNED_FLOAT:
case UNSIGNED_FIX:
return 0;
default:
return 0;
}
}
/* Nonzero only if this *really* is a single insn operand. */
int
strict_single_insn_op_p (op, mode)
rtx op;
enum machine_mode mode;
{
if (mode == VOIDmode)
mode = GET_MODE (op);
switch (GET_CODE (op))
{
case CC0:
return 1;
case CONST_INT:
if (SMALL_INT (op))
return 1;
/* We can put this set insn into delay slot, because this is one
insn; 'sethi'. */
if ((INTVAL (op) & 0x3ff) == 0)
return 1;
return 0;
case SYMBOL_REF:
return 0;
case REG:
#if 0
/* This loses when moving an freg to a general reg. */
return HARD_REGNO_NREGS (REGNO (op), mode) == 1;
#endif
return (mode != DFmode && mode != DImode);
case MEM:
if (! CONSTANT_ADDRESS_P (XEXP (op, 0)))
return (mode != DFmode && mode != DImode);
return 0;
/* We never need to negate or complement constants. */
case NEG:
return (mode != DFmode);
case NOT:
case ZERO_EXTEND:
return 1;
case PLUS:
case MINUS:
/* Detect cases that require multiple instructions. */
if (CONSTANT_P (XEXP (op, 1))
&& !(GET_CODE (XEXP (op, 1)) == CONST_INT
&& SMALL_INT (XEXP (op, 1))))
return 0;
case EQ:
case NE:
case LT:
case GT:
case LE:
case GE:
case LTU:
case GTU:
case LEU:
case GEU:
return 1;
case AND:
if (GET_CODE (XEXP (op, 1)) == NOT)
{
rtx arg = XEXP (XEXP (op, 1), 0);
if (CONSTANT_P (arg)
&& !(GET_CODE (arg) == CONST_INT
&& (SMALL_INT (arg)
|| INTVAL (arg) & 0xffff == 0)))
return 0;
}
case IOR:
case XOR:
/* Both small and round numbers take one instruction;
others take two. */
if (CONSTANT_P (XEXP (op, 1))
&& !(GET_CODE (XEXP (op, 1)) == CONST_INT
&& (SMALL_INT (XEXP (op, 1))
|| INTVAL (XEXP (op, 1)) & 0xffff == 0)))
return 0;
case LSHIFT:
case ASHIFT:
case ASHIFTRT:
case LSHIFTRT:
return 1;
case SUBREG:
if (SUBREG_WORD (op) != 0)
return 0;
return strict_single_insn_op_p (SUBREG_REG (op), mode);
case SIGN_EXTEND:
if (GET_CODE (XEXP (op, 0)) == MEM
&& ! CONSTANT_ADDRESS_P (XEXP (XEXP (op, 0), 0)))
return 1;
return 0;
/* Not doing floating point, since they probably
take longer than the branch slot they might fill. */
case FLOAT_EXTEND:
case FLOAT_TRUNCATE:
case FLOAT:
case FIX:
case UNSIGNED_FLOAT:
case UNSIGNED_FIX:
return 0;
default:
return 0;
}
}
/* Return truth value of whether OP is a relational operator. */
int
relop (op, mode)
rtx op;
enum machine_mode mode;
{
switch (GET_CODE (op))
{
case EQ:
case NE:
case GT:
case GE:
case LT:
case LE:
case GTU:
case GEU:
case LTU:
case LEU:
return 1;
}
return 0;
}
/* Return truth value of whether OP can be used as an operands in a three
address add/subtract insn (such as add %o1,7,%l2) of mode MODE. */
int
arith_operand (op, mode)
rtx op;
enum machine_mode mode;
{
return (register_operand (op, mode)
|| (GET_CODE (op) == CONST_INT && SMALL_INT (op)));
}
/* Return 1 if OP is a valid first operand for a logical insn of mode MODE. */
int
logic_operand (op, mode)
rtx op;
enum machine_mode mode;
{
return (register_operand (op, mode)
|| (GET_CODE (op) == CONST_INT && LOGIC_INT (op)));
}
/* Return 1 if OP is a valid first operand for either a logical insn
or an add insn of mode MODE. */
int
compare_operand (op, mode)
rtx op;
enum machine_mode mode;
{
return (register_operand (op, mode)
|| (GET_CODE (op) == CONST_INT && SMALL_INT (op) && LOGIC_INT (op)));
}
/* Return truth value of whether OP can be used as an operand
of a bte insn. */
int
bte_operand (op, mode)
rtx op;
enum machine_mode mode;
{
return (register_operand (op, mode)
|| (GET_CODE (op) == CONST_INT
&& (unsigned) INTVAL (op) < 0x20));
}
/* Return 1 if OP is an indexed memory reference of mode MODE. */
int
indexed_operand (op, mode)
rtx op;
enum machine_mode mode;
{
return (GET_CODE (op) == MEM && GET_MODE (op) == mode
&& GET_CODE (XEXP (op, 0)) == PLUS
&& GET_MODE (XEXP (op, 0)) == SImode
&& register_operand (XEXP (XEXP (op, 0), 0), SImode)
&& register_operand (XEXP (XEXP (op, 0), 1), SImode));
}
/* Return 1 if OP is a suitable source operand for a load insn
with mode MODE. */
int
load_operand (op, mode)
rtx op;
enum machine_mode mode;
{
return (memory_operand (op, mode) || indexed_operand (op, mode));
}
/* Return truth value of whether OP is a integer which fits the
range constraining immediate operands in add/subtract insns. */
int
small_int (op, mode)
rtx op;
enum machine_mode mode;
{
return (GET_CODE (op) == CONST_INT && SMALL_INT (op));
}
/* Return truth value of whether OP is a integer which fits the
range constraining immediate operands in logic insns. */
int
logic_int (op, mode)
rtx op;
enum machine_mode mode;
{
return (GET_CODE (op) == CONST_INT && LOGIC_INT (op));
}
/* 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)
{
if (GET_CODE (operands[1]) != MEM)
if (CONSTANT_ADDRESS_P (XEXP (operands[0], 0)))
{
if (! ((cc_prev_status.flags & CC_KNOW_HI_R31)
&& (cc_prev_status.flags & CC_HI_R31_ADJ)
&& cc_prev_status.mdep == XEXP (operands[0], 0)))
output_asm_insn ("orh ha%%%m0,r0,r31", operands);
cc_status.flags |= CC_KNOW_HI_R31 | CC_HI_R31_ADJ;
cc_status.mdep = XEXP (operands[0], 0);
return "st.l %r1,l%%%m0(r31)";
}
else
return "st.l %r1,%0";
else
abort ();
#if 0
{
rtx xoperands[2];
cc_status.flags &= ~CC_F0_IS_0;
xoperands[0] = gen_rtx (REG, SFmode, 32);
xoperands[1] = operands[1];
output_asm_insn (singlemove_string (xoperands), xoperands);
xoperands[1] = xoperands[0];
xoperands[0] = operands[0];
output_asm_insn (singlemove_string (xoperands), xoperands);
return "";
}
#endif
}
if (GET_CODE (operands[1]) == MEM)
{
if (CONSTANT_ADDRESS_P (XEXP (operands[1], 0)))
{
if (! ((cc_prev_status.flags & CC_KNOW_HI_R31)
&& (cc_prev_status.flags & CC_HI_R31_ADJ)
&& cc_prev_status.mdep == XEXP (operands[1], 0)))
output_asm_insn ("orh ha%%%m1,r0,r31", operands);
cc_status.flags |= CC_KNOW_HI_R31 | CC_HI_R31_ADJ;
cc_status.mdep = XEXP (operands[1], 0);
return "ld.l l%%%m1(r31),%0";
}
return "ld.l %1,%0";
}
return "mov %1,%0";
}
/* 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));
/* ??? Perhaps in some cases move double words
if there is a spare pair of floating regs. */
/* 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)
split_double (operands[1], &operands[1], &latehalf[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.
RMS says "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."
but it happens on the sparc when loading parameter registers,
so I am going to define that circumstance, and make it work
as expected. */
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 ("adds 0x4,%0,%0", &addreg0);
if (addreg1)
output_asm_insn ("adds 0x4,%0,%0", &addreg1);
/* Do that word. */
output_asm_insn (singlemove_string (latehalf), latehalf);
/* Undo the adds we just did. */
if (addreg0)
output_asm_insn ("adds -0x4,%0,%0", &addreg0);
if (addreg1)
output_asm_insn ("adds -0x4,%0,%0", &addreg1);
/* Do low-numbered word. */
return singlemove_string (operands);
}
else if (optype0 == REGOP && optype1 != REGOP
&& reg_overlap_mentioned_p (operands[0], operands[1]))
{
/* 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);
/* Make any unoffsettable addresses point at high-numbered word. */
if (addreg0)
output_asm_insn ("adds 0x4,%0,%0", &addreg0);
if (addreg1)
output_asm_insn ("adds 0x4,%0,%0", &addreg1);
/* Do that word. */
output_asm_insn (singlemove_string (latehalf), latehalf);
/* Undo the adds we just did. */
if (addreg0)
output_asm_insn ("adds -0x4,%0,%0", &addreg0);
if (addreg1)
output_asm_insn ("adds -0x4,%0,%0", &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.dd %1,%0";
if (GET_CODE (operands[1]) == REG)
{
output_asm_insn ("ixfr %1,%0", operands);
operands[0] = gen_rtx (REG, VOIDmode, REGNO (operands[0]) + 1);
operands[1] = gen_rtx (REG, VOIDmode, REGNO (operands[1]) + 1);
return "ixfr %1,%0";
}
if (operands[1] == dconst0_rtx)
return "fmov.dd f0,%0";
if (CONSTANT_ADDRESS_P (XEXP (operands[1], 0)))
{
if (! ((cc_prev_status.flags & CC_KNOW_HI_R31)
&& (cc_prev_status.flags & CC_HI_R31_ADJ)
&& cc_prev_status.mdep == XEXP (operands[1], 0)))
output_asm_insn ("orh ha%%%m1,r0,r31", operands);
cc_status.flags |= CC_KNOW_HI_R31 | CC_HI_R31_ADJ;
cc_status.mdep = XEXP (operands[1], 0);
return "fld.d l%%%m1(r31),%0";
}
return "fld.d %1,%0";
}
else if (FP_REG_P (operands[1]))
{
if (GET_CODE (operands[0]) == REG)
{
output_asm_insn ("fxfr %1,%0", operands);
operands[0] = gen_rtx (REG, VOIDmode, REGNO (operands[0]) + 1);
operands[1] = gen_rtx (REG, VOIDmode, REGNO (operands[1]) + 1);
return "fxfr %1,%0";
}
if (CONSTANT_ADDRESS_P (XEXP (operands[0], 0)))
{
if (! ((cc_prev_status.flags & CC_KNOW_HI_R31)
&& (cc_prev_status.flags & CC_HI_R31_ADJ)
&& cc_prev_status.mdep == XEXP (operands[0], 0)))
output_asm_insn ("orh ha%%%m0,r0,r31", operands);
cc_status.flags |= CC_KNOW_HI_R31 | CC_HI_R31_ADJ;
cc_status.mdep = XEXP (operands[0], 0);
return "fst.d %1,l%%%m0(r31)";
}
return "fst.d %1,%0";
}
else abort ();
}
/* 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 ();
}
/* Return a template for a load instruction with mode MODE and
arguments from the string ARGS.
This string is in static storage. */
static char *
load_opcode (mode, args, reg)
enum machine_mode mode;
char *args;
rtx reg;
{
static char buf[30];
char *opcode;
switch (mode)
{
case QImode:
opcode = "ld.b";
break;
case HImode:
opcode = "ld.s";
break;
case SImode:
case SFmode:
if (FP_REG_P (reg))
opcode = "fld.l";
else
opcode = "ld.l";
break;
case DImode:
if (!FP_REG_P (reg))
abort ();
case DFmode:
opcode = "fld.d";
break;
default:
abort ();
}
sprintf (buf, "%s %s", opcode, args);
return buf;
}
/* Return a template for a store instruction with mode MODE and
arguments from the string ARGS.
This string is in static storage. */
static char *
store_opcode (mode, args, reg)
enum machine_mode mode;
char *args;
rtx reg;
{
static char buf[30];
char *opcode;
switch (mode)
{
case QImode:
opcode = "st.b";
break;
case HImode:
opcode = "st.s";
break;
case SImode:
case SFmode:
if (FP_REG_P (reg))
opcode = "fst.l";
else
opcode = "st.l";
break;
case DImode:
if (!FP_REG_P (reg))
abort ();
case DFmode:
opcode = "fst.d";
break;
default:
abort ();
}
sprintf (buf, "%s %s", opcode, args);
return buf;
}
/* Output a store-in-memory whose operands are OPERANDS[0,1].
OPERANDS[0] is a MEM, and OPERANDS[1] is a reg or zero.
This function returns a template for an insn.
This is in static storage.
It may also output some insns directly.
It may alter the values of operands[0] and operands[1]. */
char *
output_store (operands)
rtx *operands;
{
enum machine_mode mode = GET_MODE (operands[0]);
rtx address = XEXP (operands[0], 0);
char *string;
cc_status.flags |= CC_KNOW_HI_R31 | CC_HI_R31_ADJ;
cc_status.mdep = address;
if (! ((cc_prev_status.flags & CC_KNOW_HI_R31)
&& (cc_prev_status.flags & CC_HI_R31_ADJ)
&& address == cc_prev_status.mdep))
{
output_asm_insn ("orh ha%%%m0,r0,r31", operands);
cc_prev_status.mdep = address;
}
/* Store zero in two parts when appropriate. */
if (mode == DFmode && operands[1] == dconst0_rtx)
return store_opcode (DFmode, "%r1,l%%%m0(r31)", operands[1]);
/* Code below isn't smart enough to move a doubleword in two parts,
so use output_move_double to do that in the cases that require it. */
if ((mode == DImode || mode == DFmode)
&& ! FP_REG_P (operands[1]))
return output_move_double (operands);
return store_opcode (mode, "%r1,l%%%m0(r31)", operands[1]);
}
/* Output a load-from-memory whose operands are OPERANDS[0,1].
OPERANDS[0] is a reg, and OPERANDS[1] is a mem.
This function returns a template for an insn.
This is in static storage.
It may also output some insns directly.
It may alter the values of operands[0] and operands[1]. */
char *
output_load (operands)
rtx *operands;
{
enum machine_mode mode = GET_MODE (operands[0]);
rtx address = XEXP (operands[1], 0);
/* We don't bother trying to see if we know %hi(address).
This is because we are doing a load, and if we know the
%hi value, we probably also know that value in memory. */
cc_status.flags |= CC_KNOW_HI_R31 | CC_HI_R31_ADJ;
cc_status.mdep = address;
if (! ((cc_prev_status.flags & CC_KNOW_HI_R31)
&& (cc_prev_status.flags & CC_HI_R31_ADJ)
&& address == cc_prev_status.mdep
&& cc_prev_status.mdep == cc_status.mdep))
{
output_asm_insn ("orh ha%%%m1,r0,r31", operands);
cc_prev_status.mdep = address;
}
/* Code below isn't smart enough to move a doubleword in two parts,
so use output_move_double to do that in the cases that require it. */
if ((mode == DImode || mode == DFmode)
&& ! FP_REG_P (operands[0]))
return output_move_double (operands);
return load_opcode (mode, "l%%%m1(r31),%0", operands[0]);
}
/* Load the address specified by OPERANDS[3] into the register
specified by OPERANDS[0].
OPERANDS[3] may be the result of a sum, hence it could either be:
(1) CONST
(2) REG
(2) REG + CONST_INT
(3) REG + REG + CONST_INT
(4) REG + REG (special case of 3).
Note that (3) is not a legitimate address.
All cases are handled here. */
void
output_load_address (operands)
rtx *operands;
{
rtx base, offset;
if (CONSTANT_P (operands[3]))
{
output_asm_insn ("mov %3,%0", operands);
return;
}
if (REG_P (operands[3]))
{
if (REGNO (operands[0]) != REGNO (operands[3]))
output_asm_insn ("mov %3,%0", operands);
return;
}
if (GET_CODE (operands[3]) != PLUS)
abort ();
base = XEXP (operands[3], 0);
offset = XEXP (operands[3], 1);
if (GET_CODE (base) == CONST_INT)
{
rtx tmp = base;
base = offset;
offset = tmp;
}
if (GET_CODE (offset) != CONST_INT)
{
/* Operand is (PLUS (REG) (REG)). */
base = operands[3];
offset = const0_rtx;
}
if (REG_P (base))
{
operands[6] = base;
operands[7] = offset;
if (SMALL_INT (offset))
output_asm_insn ("adds %7,%6,%0", operands);
else
output_asm_insn ("mov %7,%0\n\tadds %0,%6,%0", operands);
}
else if (GET_CODE (base) == PLUS)
{
operands[6] = XEXP (base, 0);
operands[7] = XEXP (base, 1);
operands[8] = offset;
if (SMALL_INT (offset))
output_asm_insn ("adds %6,%7,%0\n\tadds %8,%0,%0", operands);
else
output_asm_insn ("mov %8,%0\n\tadds %0,%6,%0\n\tadds %0,%7,%0", operands);
}
else
abort ();
}
/* Output code to place a size count SIZE in register REG.
Because block moves are pipelined, we don't include the
first element in the transfer of SIZE to REG.
For this, we subtract ALIGN. (Actually, I think it is not
right to subtract on this machine, so right now we don't.) */
static void
output_size_for_block_move (size, reg, align)
rtx size, reg, align;
{
rtx xoperands[3];
xoperands[0] = reg;
xoperands[1] = size;
xoperands[2] = align;
#if 1
cc_status.flags &= ~ CC_KNOW_HI_R31;
output_asm_insn ("mov %1,%0", xoperands);
#else
if (GET_CODE (size) == REG)
output_asm_insn ("sub %2,%1,%0", xoperands);
else
{
xoperands[1]
= gen_rtx (CONST_INT, VOIDmode, INTVAL (size) - INTVAL (align));
cc_status.flags &= ~ CC_KNOW_HI_R31;
output_asm_insn ("mov %1,%0", xoperands);
}
#endif
}
/* Emit code to perform a block move.
OPERANDS[0] is the destination.
OPERANDS[1] is the source.
OPERANDS[2] is the size.
OPERANDS[3] is the known safe alignment.
OPERANDS[4..6] are pseudos we can safely clobber as temps. */
char *
output_block_move (operands)
rtx *operands;
{
/* A vector for our computed operands. Note that load_output_address
makes use of (and can clobber) up to the 8th element of this vector. */
rtx xoperands[10];
rtx zoperands[10];
static int movstrsi_label = 0;
int i, j;
rtx temp1 = operands[4];
rtx alignrtx = operands[3];
int align = INTVAL (alignrtx);
xoperands[0] = operands[0];
xoperands[1] = operands[1];
xoperands[2] = temp1;
/* We can't move more than four bytes at a time
because we have only one register to move them through. */
if (align > 4)
{
align = 4;
alignrtx = gen_rtx (CONST_INT, VOIDmode, 4);
}
/* Since we clobber untold things, nix the condition codes. */
CC_STATUS_INIT;
/* Recognize special cases of block moves. These occur
when GNU C++ is forced to treat something as BLKmode
to keep it in memory, when its mode could be represented
with something smaller.
We cannot do this for global variables, since we don't know
what pages they don't cross. Sigh. */
if (GET_CODE (operands[2]) == CONST_INT
&& INTVAL (operands[2]) <= 16
&& ! CONSTANT_ADDRESS_P (operands[0])
&& ! CONSTANT_ADDRESS_P (operands[1]))
{
int size = INTVAL (operands[2]);
rtx op0 = xoperands[0];
rtx op1 = xoperands[1];
cc_status.flags &= ~CC_KNOW_HI_R31;
if (align == 1)
{
if (memory_address_p (QImode, plus_constant (op0, size))
&& memory_address_p (QImode, plus_constant (op1, size)))
{
for (i = size-1; i >= 0; i--)
{
xoperands[0] = plus_constant (op0, i);
xoperands[1] = plus_constant (op1, i);
output_asm_insn ("ld.b %a1,r31\n\tst.b r31,%a0",
xoperands);
}
return "";
}
}
else if (align == 2)
{
if (memory_address_p (HImode, plus_constant (op0, size))
&& memory_address_p (HImode, plus_constant (op1, size)))
{
for (i = (size>>1)-1; i >= 0; i--)
{
xoperands[0] = plus_constant (op0, i * 2);
xoperands[1] = plus_constant (op1, i * 2);
output_asm_insn ("ld.s %a1,r31\n\tst.s r31,%a0",
xoperands);
}
return "";
}
}
else
{
if (memory_address_p (SImode, plus_constant (op0, size))
&& memory_address_p (SImode, plus_constant (op1, size)))
{
for (i = (size>>2)-1; i >= 0; i--)
{
xoperands[0] = plus_constant (op0, i * 4);
xoperands[1] = plus_constant (op1, i * 4);
output_asm_insn ("ld.l %a1,r31\n\tst.l r31,%a0",
xoperands);
}
return "";
}
}
}
/* This is the size of the transfer.
Either use the register which already contains the size,
or use a free register (used by no operands). */
output_size_for_block_move (operands[2], operands[4], alignrtx);
#if 0
/* Also emit code to decrement the size value by ALIGN. */
zoperands[0] = operands[0];
zoperands[3] = plus_constant (operands[0], align);
output_load_address (zoperands);
#endif
/* Generate number for unique label. */
xoperands[3] = gen_rtx (CONST_INT, VOIDmode, movstrsi_label++);
/* Copy the increment (negative) to a register for bla insn. */
xoperands[4] = gen_rtx (CONST_INT, VOIDmode, - align);
xoperands[5] = operands[5];
output_asm_insn ("mov %4,%5", xoperands);
/* Make available a register which is a temporary. */
xoperands[6] = operands[6];
/* Now the actual loop.
In xoperands, elements 1 and 0 are the input and output vectors.
Element 2 is the loop index. Element 5 is the increment. */
if (align == 1)
{
output_asm_insn ("bla %5,%2,.Lm%3", xoperands);
output_asm_insn ("adds %0,%2,%6\n.Lm%3:", xoperands);
xoperands[3] = gen_rtx (CONST_INT, VOIDmode, movstrsi_label++);
output_asm_insn ("adds %1,%2,%1", xoperands);
output_asm_insn ("adds %5,%2,%2\n.Lm%3:", xoperands);
output_asm_insn ("ld.b 0(%1),r31", xoperands);
output_asm_insn ("adds %5,%1,%1", xoperands);
output_asm_insn ("st.b r31,0(%6)", xoperands);
output_asm_insn ("bla %5,%2,.Lm%3", xoperands);
output_asm_insn ("adds %5,%6,%6", xoperands);
}
if (align == 2)
{
output_asm_insn ("bla %5,%2,.Lm%3", xoperands);
output_asm_insn ("adds %0,%2,%6\n.Lm%3:", xoperands);
xoperands[3] = gen_rtx (CONST_INT, VOIDmode, movstrsi_label++);
output_asm_insn ("adds %1,%2,%1", xoperands);
output_asm_insn ("adds %5,%2,%2\n.Lm%3:", xoperands);
output_asm_insn ("ld.s 0(%1),r31", xoperands);
output_asm_insn ("adds %5,%1,%1", xoperands);
output_asm_insn ("st.s r31,0(%6)", xoperands);
output_asm_insn ("bla %5,%2,.Lm%3", xoperands);
output_asm_insn ("adds %5,%6,%6", xoperands);
}
if (align == 4)
{
output_asm_insn ("bla %5,%2,.Lm%3", xoperands);
output_asm_insn ("adds %0,%2,%6\n.Lm%3:", xoperands);
xoperands[3] = gen_rtx (CONST_INT, VOIDmode, movstrsi_label++);
output_asm_insn ("adds %1,%2,%1", xoperands);
output_asm_insn ("adds %5,%2,%2\n.Lm%3:", xoperands);
output_asm_insn ("ld.l 0(%1),r31", xoperands);
output_asm_insn ("adds %5,%1,%1", xoperands);
output_asm_insn ("st.l r31,0(%6)", xoperands);
output_asm_insn ("bla %5,%2,.Lm%3", xoperands);
output_asm_insn ("adds %5,%6,%6", xoperands);
}
return "";
}
/* Output a delayed branch insn with the delay insn in its
branch slot. The delayed branch insn template is in TEMPLATE,
with operands OPERANDS. The insn in its delay slot is INSN.
As a special case, since we know that all memory transfers are via
ld/st insns, if we see a (MEM (SYMBOL_REF ...)) we divide the memory
reference around the branch as
orh ha%x,r0,r31
b ...
ld/st l%x(r31),...
As another special case, we handle loading (SYMBOL_REF ...) and
other large constants around branches as well:
orh h%x,r0,%0
b ...
or l%x,%0,%1
*/
char *
output_delayed_branch (template, operands, insn)
char *template;
rtx *operands;
rtx insn;
{
extern rtx recog_operand[];
rtx src = XVECEXP (PATTERN (insn), 0, 1);
rtx dest = XVECEXP (PATTERN (insn), 0, 0);
if (GET_CODE (src) == SYMBOL_REF || GET_CODE (src) == CONST
|| (GET_CODE (src) == CONST_INT
&& !(SMALL_INT (src) || (INTVAL (src) & 0xffff) == 0)))
{
rtx xoperands[2];
xoperands[0] = dest;
xoperands[1] = src;
/* Output the `orh' insn. */
output_asm_insn ("orh h%%%1,r0,%0", xoperands);
/* Output the branch instruction next. */
output_asm_insn (template, operands);
/* Now output the `or' insn. */
output_asm_insn ("or l%%%1,%0,%0", xoperands);
}
else if ((GET_CODE (src) == MEM
&& CONSTANT_ADDRESS_P (XEXP (src, 0)))
|| (GET_CODE (dest) == MEM
&& CONSTANT_ADDRESS_P (XEXP (dest, 0))))
{
rtx xoperands[2];
char *split_template;
xoperands[0] = dest;
xoperands[1] = src;
/* Output the `orh' insn. */
if (GET_CODE (src) == MEM)
{
if (! ((cc_prev_status.flags & CC_KNOW_HI_R31)
&& (cc_prev_status.flags & CC_HI_R31_ADJ)
&& cc_prev_status.mdep == XEXP (operands[1], 0)))
output_asm_insn ("orh ha%%%m1,r0,r31", xoperands);
split_template = load_opcode (GET_MODE (dest),
"l%%%m1(r31),%0", dest);
}
else
{
if (! ((cc_prev_status.flags & CC_KNOW_HI_R31)
&& (cc_prev_status.flags & CC_HI_R31_ADJ)
&& cc_prev_status.mdep == XEXP (operands[0], 0)))
output_asm_insn ("orh ha%%%m0,r0,r31", xoperands);
split_template = store_opcode (GET_MODE (dest),
"%r1,l%%%m0(r31)", src);
}
/* Output the branch instruction next. */
output_asm_insn (template, operands);
/* Now output the load or store.
No need to do a CC_STATUS_INIT, because we are branching anyway. */
output_asm_insn (split_template, xoperands);
}
else
{
extern char *insn_template[];
extern char *(*insn_outfun[])();
extern int insn_n_operands[];
extern rtx alter_subreg();
int insn_code_number;
rtx pat = gen_rtx (SET, VOIDmode, dest, src);
rtx delay_insn = gen_rtx (INSN, VOIDmode, 0, 0, 0, pat, -1, 0, 0);
int i;
/* Output the branch instruction first. */
output_asm_insn (template, operands);
/* Now recognize the insn which we put in its delay slot.
We must do this after outputing the branch insn,
since operands may just be a pointer to `recog_operand'. */
insn_code_number = recog (pat, delay_insn);
if (insn_code_number == -1)
abort ();
for (i = 0; i < insn_n_operands[insn_code_number]; i++)
{
if (GET_CODE (recog_operand[i]) == SUBREG)
recog_operand[i] = alter_subreg (recog_operand[i]);
}
/* Now get the template for what this insn would
have been, without the branch. Its operands are
exactly the same as they would be, so we don't
need to do an insn_extract. */
template = insn_template[insn_code_number];
if (template == 0)
template = (*insn_outfun[insn_code_number]) (recog_operand, delay_insn);
output_asm_insn (template, recog_operand);
}
CC_STATUS_INIT;
return "";
}
/* Output a newly constructed insn DELAY_INSN. */
char *
output_delay_insn (delay_insn)
rtx delay_insn;
{
char *template;
extern rtx recog_operand[];
extern char call_used_regs[];
extern char *insn_template[];
extern int insn_n_operands[];
extern char *(*insn_outfun[])();
extern rtx alter_subreg();
int insn_code_number;
extern int insn_n_operands[];
int i;
/* Now recognize the insn which we put in its delay slot.
We must do this after outputing the branch insn,
since operands may just be a pointer to `recog_operand'. */
insn_code_number = recog_memoized (delay_insn);
if (insn_code_number == -1)
abort ();
/* Extract the operands of this delay insn. */
INSN_CODE (delay_insn) = insn_code_number;
insn_extract (delay_insn);
/* It is possible that this insn has not been properly scaned by final
yet. If this insn's operands don't appear in the peephole's
actual operands, then they won't be fixed up by final, so we
make sure they get fixed up here. -- This is a kludge. */
for (i = 0; i < insn_n_operands[insn_code_number]; i++)
{
if (GET_CODE (recog_operand[i]) == SUBREG)
recog_operand[i] = alter_subreg (recog_operand[i]);
}
#ifdef REGISTER_CONSTRAINTS
if (! constrain_operands (insn_code_number))
abort ();
#endif
cc_prev_status = cc_status;
/* Update `cc_status' for this instruction.
The instruction's output routine may change it further.
If the output routine for a jump insn needs to depend
on the cc status, it should look at cc_prev_status. */
NOTICE_UPDATE_CC (PATTERN (delay_insn), delay_insn);
/* Now get the template for what this insn would
have been, without the branch. */
template = insn_template[insn_code_number];
if (template == 0)
template = (*insn_outfun[insn_code_number]) (recog_operand, delay_insn);
output_asm_insn (template, recog_operand);
return "";
}