865 lines
21 KiB
C
865 lines
21 KiB
C
|
/* Subroutines for insn-output.c for Pyramid 90x, 9000, and MIServer Series.
|
|||
|
Copyright (C) 1989, 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. */
|
|||
|
|
|||
|
/* Some output-actions in pyr.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"
|
|||
|
#include "tree.h"
|
|||
|
|
|||
|
/*
|
|||
|
* Do FUNCTION_ARG.
|
|||
|
* This cannot be defined as a macro on pyramids, because Pyramid Technology's
|
|||
|
* C compiler dies on (several equivalent definitions of) this macro.
|
|||
|
* The only way around this cc bug was to make this a function.
|
|||
|
* While it would be possible to use a macro version for gcc, it seems
|
|||
|
* more reliable to have a single version of the code.
|
|||
|
*/
|
|||
|
void *
|
|||
|
pyr_function_arg(cum, mode, type, named)
|
|||
|
CUMULATIVE_ARGS cum;
|
|||
|
enum machine_mode mode;
|
|||
|
tree type;
|
|||
|
{
|
|||
|
return (void *)(FUNCTION_ARG_HELPER (cum, mode,type,named));
|
|||
|
}
|
|||
|
|
|||
|
/* Do the hard part of PARAM_SAFE_FOR_REG_P.
|
|||
|
* This cannot be defined as a macro on pyramids, because Pyramid Technology's
|
|||
|
* C compiler dies on (several equivalent definitions of) this macro.
|
|||
|
* The only way around this cc bug was to make this a function.
|
|||
|
*/
|
|||
|
int
|
|||
|
inner_param_safe_helper (type)
|
|||
|
tree type;
|
|||
|
{
|
|||
|
return (INNER_PARAM_SAFE_HELPER(type));
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Return 1 if OP is a non-indexed operand of mode MODE.
|
|||
|
This is either a register reference, a memory reference,
|
|||
|
or a constant. In the case of a memory reference, the address
|
|||
|
is checked to make sure it isn't indexed.
|
|||
|
|
|||
|
Register and memory references must have mode MODE in order to be valid,
|
|||
|
but some constants have no machine mode and are valid for any mode.
|
|||
|
|
|||
|
If MODE is VOIDmode, OP is checked for validity for whatever mode
|
|||
|
it has.
|
|||
|
|
|||
|
The main use of this function is as a predicate in match_operand
|
|||
|
expressions in the machine description.
|
|||
|
|
|||
|
It is useful to compare this with general_operand(). They should
|
|||
|
be identical except for one line.
|
|||
|
|
|||
|
This function seems necessary because of the non-orthogonality of
|
|||
|
Pyramid insns.
|
|||
|
For any 2-operand insn, and any combination of operand modes,
|
|||
|
if indexing is valid for the isn's second operand, it is invalid
|
|||
|
for the first operand to be indexed. */
|
|||
|
|
|||
|
extern int volatile_ok;
|
|||
|
|
|||
|
int
|
|||
|
nonindexed_operand (op, mode)
|
|||
|
register rtx op;
|
|||
|
enum machine_mode mode;
|
|||
|
{
|
|||
|
register RTX_CODE code = GET_CODE (op);
|
|||
|
int mode_altering_drug = 0;
|
|||
|
|
|||
|
if (mode == VOIDmode)
|
|||
|
mode = GET_MODE (op);
|
|||
|
|
|||
|
/* Don't accept CONST_INT or anything similar
|
|||
|
if the caller wants something floating. */
|
|||
|
if (GET_MODE (op) == VOIDmode && mode != VOIDmode
|
|||
|
&& GET_MODE_CLASS (mode) != MODE_INT)
|
|||
|
return 0;
|
|||
|
|
|||
|
if (CONSTANT_P (op))
|
|||
|
return ((GET_MODE (op) == VOIDmode || GET_MODE (op) == mode)
|
|||
|
&& LEGITIMATE_CONSTANT_P (op));
|
|||
|
|
|||
|
/* Except for certain constants with VOIDmode, already checked for,
|
|||
|
OP's mode must match MODE if MODE specifies a mode. */
|
|||
|
|
|||
|
if (GET_MODE (op) != mode)
|
|||
|
return 0;
|
|||
|
|
|||
|
while (code == SUBREG)
|
|||
|
{
|
|||
|
op = SUBREG_REG (op);
|
|||
|
code = GET_CODE (op);
|
|||
|
#if 0
|
|||
|
/* No longer needed, since (SUBREG (MEM...))
|
|||
|
will load the MEM into a reload reg in the MEM's own mode. */
|
|||
|
mode_altering_drug = 1;
|
|||
|
#endif
|
|||
|
}
|
|||
|
if (code == REG)
|
|||
|
return 1;
|
|||
|
if (code == CONST_DOUBLE)
|
|||
|
return LEGITIMATE_CONSTANT_P (op);
|
|||
|
if (code == MEM)
|
|||
|
{
|
|||
|
register rtx y = XEXP (op, 0);
|
|||
|
if (! volatile_ok && MEM_VOLATILE_P (op))
|
|||
|
return 0;
|
|||
|
GO_IF_NONINDEXED_ADDRESS (y, win);
|
|||
|
}
|
|||
|
return 0;
|
|||
|
|
|||
|
win:
|
|||
|
if (mode_altering_drug)
|
|||
|
return ! mode_dependent_address_p (XEXP (op, 0));
|
|||
|
return 1;
|
|||
|
}
|
|||
|
|
|||
|
/* Return non-zero if the rtx OP has an immediate component. An
|
|||
|
immediate component or additive term equal to zero is rejected
|
|||
|
due to assembler problems. */
|
|||
|
|
|||
|
int
|
|||
|
has_direct_base (op)
|
|||
|
rtx op;
|
|||
|
{
|
|||
|
if ((CONSTANT_ADDRESS_P (op)
|
|||
|
&& op != const0_rtx)
|
|||
|
|| (GET_CODE (op) == PLUS
|
|||
|
&& ((CONSTANT_ADDRESS_P (XEXP (op, 1))
|
|||
|
&& XEXP (op, 1) != const0_rtx)
|
|||
|
|| (CONSTANT_ADDRESS_P (XEXP (op, 0))
|
|||
|
&& XEXP (op, 0) != const0_rtx))))
|
|||
|
return 1;
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
/* Return zero if the rtx OP has a (scaled) index. */
|
|||
|
|
|||
|
int
|
|||
|
has_index (op)
|
|||
|
rtx op;
|
|||
|
{
|
|||
|
if (GET_CODE (op) == PLUS
|
|||
|
&& (GET_CODE (XEXP (op, 0)) == MULT
|
|||
|
|| (GET_CODE (XEXP (op, 1)) == MULT)))
|
|||
|
return 1;
|
|||
|
else
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
int swap_operands;
|
|||
|
|
|||
|
/* weird_memory_memory -- return 1 if OP1 and OP2 can be compared (or
|
|||
|
exchanged with xchw) with one instruction. If the operands need to
|
|||
|
be swapped, set the global variable SWAP_OPERANDS. This function
|
|||
|
silently assumes that both OP0 and OP1 are valid memory references.
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
weird_memory_memory (op0, op1)
|
|||
|
rtx op0, op1;
|
|||
|
{
|
|||
|
RTX_CODE code0, code1;
|
|||
|
|
|||
|
op0 = XEXP (op0, 0);
|
|||
|
op1 = XEXP (op1, 0);
|
|||
|
code0 = GET_CODE (op0);
|
|||
|
code1 = GET_CODE (op1);
|
|||
|
|
|||
|
swap_operands = 0;
|
|||
|
|
|||
|
if (code1 == REG || code1 == SUBREG)
|
|||
|
{
|
|||
|
return 1;
|
|||
|
}
|
|||
|
if (code0 == REG || code0 == SUBREG)
|
|||
|
{
|
|||
|
swap_operands = 1;
|
|||
|
return 1;
|
|||
|
}
|
|||
|
if (has_direct_base (op0) && has_direct_base (op1))
|
|||
|
{
|
|||
|
if (has_index (op1))
|
|||
|
{
|
|||
|
if (has_index (op0))
|
|||
|
return 0;
|
|||
|
swap_operands = 1;
|
|||
|
}
|
|||
|
|
|||
|
return 1;
|
|||
|
}
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
int
|
|||
|
signed_comparison (x, mode)
|
|||
|
rtx x;
|
|||
|
enum machine_mode mode;
|
|||
|
{
|
|||
|
return ! TRULY_UNSIGNED_COMPARE_P (GET_CODE (x));
|
|||
|
}
|
|||
|
|
|||
|
extern rtx force_reg ();
|
|||
|
rtx test_op0, test_op1;
|
|||
|
enum machine_mode test_mode;
|
|||
|
|
|||
|
/* Sign-extend or zero-extend constant X from FROM_MODE to TO_MODE. */
|
|||
|
|
|||
|
rtx
|
|||
|
extend_const (x, extop, from_mode, to_mode)
|
|||
|
rtx x;
|
|||
|
RTX_CODE extop;
|
|||
|
enum machine_mode from_mode, to_mode;
|
|||
|
{
|
|||
|
int val;
|
|||
|
int negative;
|
|||
|
if (from_mode == to_mode)
|
|||
|
return x;
|
|||
|
if (GET_CODE (x) != CONST_INT)
|
|||
|
abort ();
|
|||
|
val = INTVAL (x);
|
|||
|
negative = val & (1 << (GET_MODE_BITSIZE (from_mode) - 1));
|
|||
|
if (GET_MODE_BITSIZE (from_mode) == HOST_BITS_PER_INT)
|
|||
|
abort ();
|
|||
|
if (negative && extop == SIGN_EXTEND)
|
|||
|
val = val | ((-1) << (GET_MODE_BITSIZE (from_mode)));
|
|||
|
else
|
|||
|
val = val & ~((-1) << (GET_MODE_BITSIZE (from_mode)));
|
|||
|
if (GET_MODE_BITSIZE (to_mode) == HOST_BITS_PER_INT)
|
|||
|
return gen_rtx (CONST_INT, VOIDmode, val);
|
|||
|
return gen_rtx (CONST_INT, VOIDmode,
|
|||
|
val & ~((-1) << (GET_MODE_BITSIZE (to_mode))));
|
|||
|
}
|
|||
|
|
|||
|
rtx
|
|||
|
ensure_extended (op, extop, from_mode)
|
|||
|
rtx op;
|
|||
|
RTX_CODE extop;
|
|||
|
enum machine_mode from_mode;
|
|||
|
{
|
|||
|
if (GET_CODE (op) == CONST_INT)
|
|||
|
return extend_const (op, extop, from_mode, SImode);
|
|||
|
else
|
|||
|
return force_reg (SImode, gen_rtx (extop, SImode, op));
|
|||
|
}
|
|||
|
|
|||
|
/* Emit rtl for a branch, as well as any delayed (integer) compare insns.
|
|||
|
The compare insn to perform is determined by the global variables
|
|||
|
test_op0 and test_op1. */
|
|||
|
|
|||
|
void
|
|||
|
extend_and_branch (extop)
|
|||
|
RTX_CODE extop;
|
|||
|
{
|
|||
|
rtx op0, op1;
|
|||
|
RTX_CODE code0, code1;
|
|||
|
|
|||
|
op0 = test_op0, op1 = test_op1;
|
|||
|
if (op0 == 0)
|
|||
|
return;
|
|||
|
|
|||
|
code0 = GET_CODE (op0);
|
|||
|
if (op1 != 0)
|
|||
|
code1 = GET_CODE (op1);
|
|||
|
test_op0 = test_op1 = 0;
|
|||
|
|
|||
|
if (op1 == 0)
|
|||
|
{
|
|||
|
op0 = ensure_extended (op0, extop, test_mode);
|
|||
|
emit_insn (gen_rtx (SET, VOIDmode, cc0_rtx, op0));
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (CONSTANT_P (op0) && CONSTANT_P (op1))
|
|||
|
{
|
|||
|
op0 = ensure_extended (op0, extop, test_mode);
|
|||
|
op1 = ensure_extended (op1, extop, test_mode);
|
|||
|
}
|
|||
|
else if (extop == ZERO_EXTEND && test_mode == HImode)
|
|||
|
{
|
|||
|
/* Pyramids have no unsigned "cmphi" instructions. We need to
|
|||
|
zero extend unsigned halfwords into temporary registers. */
|
|||
|
op0 = ensure_extended (op0, extop, test_mode);
|
|||
|
op1 = ensure_extended (op1, extop, test_mode);
|
|||
|
}
|
|||
|
else if (CONSTANT_P (op0))
|
|||
|
{
|
|||
|
op0 = ensure_extended (op0, extop, test_mode);
|
|||
|
op1 = ensure_extended (op1, extop, test_mode);
|
|||
|
}
|
|||
|
else if (CONSTANT_P (op1))
|
|||
|
{
|
|||
|
op1 = ensure_extended (op1, extop, test_mode);
|
|||
|
op0 = ensure_extended (op0, extop, test_mode);
|
|||
|
}
|
|||
|
else if ((code0 == REG || code0 == SUBREG)
|
|||
|
&& (code1 == REG || code1 == SUBREG))
|
|||
|
{
|
|||
|
/* I could do this case without extension, by using the virtual
|
|||
|
register address (but that would lose for global regs). */
|
|||
|
op0 = ensure_extended (op0, extop, test_mode);
|
|||
|
op1 = ensure_extended (op1, extop, test_mode);
|
|||
|
}
|
|||
|
else if (code0 == MEM && code1 == MEM)
|
|||
|
{
|
|||
|
/* Load into a reg if the address combination can't be handled
|
|||
|
directly. */
|
|||
|
if (! weird_memory_memory (op0, op1))
|
|||
|
op0 = force_reg (test_mode, op0);
|
|||
|
}
|
|||
|
|
|||
|
emit_insn (gen_rtx (SET, VOIDmode, cc0_rtx,
|
|||
|
gen_rtx (COMPARE, VOIDmode, op0, op1)));
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* Return non-zero if the two single-word moves with operands[0]
|
|||
|
and operands[1] for the first single-word move, and operands[2]
|
|||
|
and operands[3] for the second single-word move, is possible to
|
|||
|
combine to a double word move.
|
|||
|
|
|||
|
The criterion is whether the operands are in consecutive memory cells,
|
|||
|
registers, etc. */
|
|||
|
|
|||
|
int
|
|||
|
movdi_possible (operands)
|
|||
|
rtx operands[];
|
|||
|
{
|
|||
|
int cnst_diff0, cnst_diff1;
|
|||
|
RTX_CODE code0 = GET_CODE (operands[0]);
|
|||
|
RTX_CODE code1 = GET_CODE (operands[1]);
|
|||
|
|
|||
|
/* Don't dare to combine (possibly overlapping) memory -> memory moves. */
|
|||
|
/* It would be possible to detect the cases where we dare, by using
|
|||
|
constant_diff (operands[0], operands[1])!!! */
|
|||
|
if (code0 == MEM && code1 == MEM)
|
|||
|
return 0;
|
|||
|
|
|||
|
cnst_diff0 = consecutive_operands (operands[0], operands[2]);
|
|||
|
if (cnst_diff0 == 0)
|
|||
|
return 0;
|
|||
|
|
|||
|
cnst_diff1 = consecutive_operands (operands[1], operands[3]);
|
|||
|
if (cnst_diff1 == 0)
|
|||
|
return 0;
|
|||
|
|
|||
|
if (cnst_diff0 & cnst_diff1)
|
|||
|
{
|
|||
|
/* The source and destination operands are consecutive. */
|
|||
|
|
|||
|
/* If the first move writes into the source of the second move,
|
|||
|
we cannot combine. */
|
|||
|
if ((code0 == REG
|
|||
|
&& reg_overlap_mentioned_p (operands[0], operands[3]))
|
|||
|
|| (code0 == SUBREG
|
|||
|
&& subreg_overlap_mentioned_p (operands[0], operands[3])))
|
|||
|
return 0;
|
|||
|
|
|||
|
if (cnst_diff0 & 1)
|
|||
|
/* operands[0],[1] has higher addresses than operands[2],[3]. */
|
|||
|
swap_operands = 0;
|
|||
|
else
|
|||
|
/* operands[0],[1] has lower addresses than operands[2],[3]. */
|
|||
|
swap_operands = 1;
|
|||
|
return 1;
|
|||
|
}
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
/* Like reg_overlap_mentioned_p, but accepts a subreg rtx instead
|
|||
|
of a reg. */
|
|||
|
|
|||
|
int
|
|||
|
subreg_overlap_mentioned_p (subreg, x)
|
|||
|
rtx subreg, x;
|
|||
|
{
|
|||
|
rtx reg = SUBREG_REG (subreg);
|
|||
|
int regno = REGNO (reg) + SUBREG_WORD (subreg);
|
|||
|
int endregno = regno + HARD_REGNO_NREGS (regno, GET_MODE (subreg));
|
|||
|
return refers_to_regno_p (regno, endregno, x, 0);
|
|||
|
}
|
|||
|
|
|||
|
/* Return 1 if OP0 is a consecutive operand to OP1, 2 if OP1 is a
|
|||
|
consecutive operand to OP0.
|
|||
|
|
|||
|
This function is used to determine if addresses are consecutive,
|
|||
|
and therefore possible to combine to fewer instructions. */
|
|||
|
|
|||
|
int
|
|||
|
consecutive_operands (op0, op1)
|
|||
|
rtx op0, op1;
|
|||
|
{
|
|||
|
RTX_CODE code0, code1;
|
|||
|
int cnst_diff;
|
|||
|
int regno_off0, regno_off1;
|
|||
|
|
|||
|
code0 = GET_CODE (op0);
|
|||
|
code1 = GET_CODE (op1);
|
|||
|
|
|||
|
regno_off0 = 0;
|
|||
|
if (code0 == SUBREG)
|
|||
|
{
|
|||
|
if (GET_MODE_SIZE (GET_MODE (SUBREG_REG (op0))) <= UNITS_PER_WORD)
|
|||
|
return 0;
|
|||
|
regno_off0 = SUBREG_WORD (op0);
|
|||
|
op0 = SUBREG_REG (op0);
|
|||
|
code0 = REG;
|
|||
|
}
|
|||
|
|
|||
|
regno_off1 = 0;
|
|||
|
if (code1 == SUBREG)
|
|||
|
{
|
|||
|
if (GET_MODE_SIZE (GET_MODE (SUBREG_REG (op1))) <= UNITS_PER_WORD)
|
|||
|
return 0;
|
|||
|
regno_off1 = SUBREG_WORD (op1);
|
|||
|
op1 = SUBREG_REG (op1);
|
|||
|
code1 = REG;
|
|||
|
}
|
|||
|
|
|||
|
if (code0 != code1)
|
|||
|
return 0;
|
|||
|
|
|||
|
switch (code0)
|
|||
|
{
|
|||
|
case CONST_INT:
|
|||
|
/* Cannot permit any symbolic constants, even if the consecutive
|
|||
|
operand is 0, since a movl really performs sign extension. */
|
|||
|
if (code1 != CONST_INT)
|
|||
|
return 0;
|
|||
|
if ((INTVAL (op0) == 0 && INTVAL (op1) == 0)
|
|||
|
|| (INTVAL (op0) == -1 && INTVAL (op1) == -1))
|
|||
|
return 3;
|
|||
|
if ((INTVAL (op0) == 0 && INTVAL (op1) > 0)
|
|||
|
|| (INTVAL (op0) == -1 && INTVAL (op1) < 0))
|
|||
|
return 2;
|
|||
|
if ((INTVAL (op1) == 0 && INTVAL (op0) > 0)
|
|||
|
|| (INTVAL (op1) == -1 && INTVAL (op0) < 0))
|
|||
|
return 1;
|
|||
|
break;
|
|||
|
|
|||
|
case REG:
|
|||
|
regno_off0 = REGNO (op0) + regno_off0;
|
|||
|
regno_off1 = REGNO (op1) + regno_off1;
|
|||
|
|
|||
|
cnst_diff = regno_off0 - regno_off1;
|
|||
|
if (cnst_diff == 1)
|
|||
|
{
|
|||
|
/* movl with the highest numbered parameter (local) register as
|
|||
|
source or destination, doesn't wrap to the lowest numbered local
|
|||
|
(temporary) register. */
|
|||
|
|
|||
|
if (regno_off0 % 16 != 0)
|
|||
|
return 1;
|
|||
|
else
|
|||
|
return 0;
|
|||
|
}
|
|||
|
else if (cnst_diff == -1)
|
|||
|
{
|
|||
|
if (regno_off1 % 16 != 0)
|
|||
|
return 2;
|
|||
|
else
|
|||
|
return 0;
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
case MEM:
|
|||
|
op0 = XEXP (op0, 0);
|
|||
|
op1 = XEXP (op1, 0);
|
|||
|
if (GET_CODE (op0) == CONST)
|
|||
|
op0 = XEXP (op0, 0);
|
|||
|
if (GET_CODE (op1) == CONST)
|
|||
|
op1 = XEXP (op1, 0);
|
|||
|
|
|||
|
cnst_diff = constant_diff (op0, op1);
|
|||
|
if (cnst_diff)
|
|||
|
{
|
|||
|
if (cnst_diff == 4)
|
|||
|
return 1;
|
|||
|
else if (cnst_diff == -4)
|
|||
|
return 2;
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
/* Return the constant difference of the rtx expressions OP0 and OP1,
|
|||
|
or 0 if they don't have a constant difference.
|
|||
|
|
|||
|
This function is used to determine if addresses are consecutive,
|
|||
|
and therefore possible to combine to fewer instructions. */
|
|||
|
|
|||
|
int
|
|||
|
constant_diff (op0, op1)
|
|||
|
rtx op0, op1;
|
|||
|
{
|
|||
|
RTX_CODE code0, code1;
|
|||
|
int cnst_diff;
|
|||
|
|
|||
|
code0 = GET_CODE (op0);
|
|||
|
code1 = GET_CODE (op1);
|
|||
|
|
|||
|
if (code0 != code1)
|
|||
|
{
|
|||
|
if (code0 == PLUS)
|
|||
|
{
|
|||
|
if (GET_CODE (XEXP (op0, 1)) == CONST_INT
|
|||
|
&& rtx_equal_p (op1, XEXP (op0, 0)))
|
|||
|
return INTVAL (XEXP (op0, 1));
|
|||
|
}
|
|||
|
else if (code1 == PLUS)
|
|||
|
{
|
|||
|
if (GET_CODE (XEXP (op1, 1)) == CONST_INT
|
|||
|
&& rtx_equal_p (op0, XEXP (op1, 0)))
|
|||
|
return -INTVAL (XEXP (op1, 1));
|
|||
|
}
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
if (code0 == CONST_INT)
|
|||
|
return INTVAL (op0) - INTVAL (op1);
|
|||
|
|
|||
|
if (code0 == PLUS)
|
|||
|
{
|
|||
|
cnst_diff = constant_diff (XEXP (op0, 0), XEXP (op1, 0));
|
|||
|
if (cnst_diff)
|
|||
|
return (rtx_equal_p (XEXP (op0, 1), XEXP (op1, 1)))
|
|||
|
? cnst_diff : 0;
|
|||
|
cnst_diff = constant_diff (XEXP (op0, 1), XEXP (op1, 1));
|
|||
|
if (cnst_diff)
|
|||
|
return (rtx_equal_p (XEXP (op0, 0), XEXP (op1, 0)))
|
|||
|
? cnst_diff : 0;
|
|||
|
}
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
int
|
|||
|
already_sign_extended (insn, from_mode, op)
|
|||
|
rtx insn;
|
|||
|
enum machine_mode from_mode;
|
|||
|
rtx op;
|
|||
|
{
|
|||
|
rtx xinsn, xdest, xsrc;
|
|||
|
|
|||
|
for (;;)
|
|||
|
{
|
|||
|
insn = PREV_INSN (insn);
|
|||
|
if (insn == 0)
|
|||
|
return 0;
|
|||
|
if (GET_CODE (insn) == NOTE || GET_CODE (insn) == JUMP_INSN)
|
|||
|
continue;
|
|||
|
if (GET_CODE (insn) == CALL_INSN && ! call_used_regs[REGNO (op)])
|
|||
|
continue;
|
|||
|
if (GET_CODE (insn) != INSN)
|
|||
|
return 0;
|
|||
|
xinsn = PATTERN (insn);
|
|||
|
|
|||
|
if (GET_CODE (xinsn) != SET)
|
|||
|
return 0;
|
|||
|
|
|||
|
xdest = SET_DEST (xinsn);
|
|||
|
xsrc = SET_SRC (xinsn);
|
|||
|
|
|||
|
if (GET_CODE (xdest) == SUBREG)
|
|||
|
abort ();
|
|||
|
|
|||
|
if ( ! REG_P (xdest))
|
|||
|
continue;
|
|||
|
|
|||
|
if (REGNO (op) == REGNO (xdest)
|
|||
|
&& ((GET_CODE (xsrc) == SIGN_EXTEND
|
|||
|
&& GET_MODE (XEXP (xsrc, 0)) == from_mode)
|
|||
|
|| (GET_CODE (xsrc) == MEM
|
|||
|
&& GET_MODE (xsrc) == from_mode)))
|
|||
|
return 1;
|
|||
|
|
|||
|
/* The register is modified by another operation. */
|
|||
|
if (reg_overlap_mentioned_p (xdest, op))
|
|||
|
return 0;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
char *
|
|||
|
output_move_double (operands)
|
|||
|
rtx *operands;
|
|||
|
{
|
|||
|
if (GET_CODE (operands[1]) == CONST_DOUBLE)
|
|||
|
{
|
|||
|
if (GET_MODE_CLASS (GET_MODE (operands[1])) == MODE_INT)
|
|||
|
{
|
|||
|
/* In an integer, the low-order word is in CONST_DOUBLE_LOW. */
|
|||
|
rtx const_op = operands[1];
|
|||
|
if ((CONST_DOUBLE_HIGH (const_op) == 0
|
|||
|
&& CONST_DOUBLE_LOW (const_op) >= 0)
|
|||
|
|| (CONST_DOUBLE_HIGH (const_op) == -1
|
|||
|
&& CONST_DOUBLE_LOW (const_op) < 0))
|
|||
|
{
|
|||
|
operands[1] = gen_rtx (CONST_INT, VOIDmode,
|
|||
|
CONST_DOUBLE_LOW (const_op));
|
|||
|
return "movl %1,%0";
|
|||
|
}
|
|||
|
operands[1] = gen_rtx (CONST_INT, VOIDmode,
|
|||
|
CONST_DOUBLE_HIGH (const_op));
|
|||
|
output_asm_insn ("movw %1,%0", operands);
|
|||
|
operands[0] = gen_rtx (REG, SImode, REGNO (operands[0]) + 1);
|
|||
|
operands[1] = gen_rtx (CONST_INT, VOIDmode,
|
|||
|
CONST_DOUBLE_LOW (const_op));
|
|||
|
return "movw %1,%0";
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
/* In a real, the low-address word is in CONST_DOUBLE_LOW. */
|
|||
|
rtx const_op = operands[1];
|
|||
|
if ((CONST_DOUBLE_LOW (const_op) == 0
|
|||
|
&& CONST_DOUBLE_HIGH (const_op) >= 0)
|
|||
|
|| (CONST_DOUBLE_LOW (const_op) == -1
|
|||
|
&& CONST_DOUBLE_HIGH (const_op) < 0))
|
|||
|
{
|
|||
|
operands[1] = gen_rtx (CONST_INT, VOIDmode,
|
|||
|
CONST_DOUBLE_HIGH (const_op));
|
|||
|
return "movl %1,%0";
|
|||
|
}
|
|||
|
operands[1] = gen_rtx (CONST_INT, VOIDmode,
|
|||
|
CONST_DOUBLE_LOW (const_op));
|
|||
|
output_asm_insn ("movw %1,%0", operands);
|
|||
|
operands[0] = gen_rtx (REG, SImode, REGNO (operands[0]) + 1);
|
|||
|
operands[1] = gen_rtx (CONST_INT, VOIDmode,
|
|||
|
CONST_DOUBLE_HIGH (const_op));
|
|||
|
return "movw %1,%0";
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return "movl %1,%0";
|
|||
|
}
|
|||
|
|
|||
|
/* Output a shift insns, after having reduced integer arguments to
|
|||
|
avoid as warnings. */
|
|||
|
|
|||
|
char *
|
|||
|
output_shift (pattern, op2, mod)
|
|||
|
char *pattern;
|
|||
|
rtx op2;
|
|||
|
int mod;
|
|||
|
{
|
|||
|
if (GET_CODE (op2) == CONST_INT)
|
|||
|
{
|
|||
|
int cnt = INTVAL (op2) % mod;
|
|||
|
if (cnt == 0)
|
|||
|
{
|
|||
|
cc_status = cc_prev_status;
|
|||
|
return "";
|
|||
|
}
|
|||
|
op2 = gen_rtx (CONST_INT, VOIDmode, cnt);
|
|||
|
}
|
|||
|
return pattern;
|
|||
|
}
|
|||
|
|
|||
|
/* Return non-zero if the code of this rtx pattern is a relop. */
|
|||
|
|
|||
|
int
|
|||
|
relop (op, mode)
|
|||
|
rtx op;
|
|||
|
enum machine_mode mode;
|
|||
|
{
|
|||
|
switch (GET_CODE (op))
|
|||
|
{
|
|||
|
case EQ:
|
|||
|
case NE:
|
|||
|
case LT:
|
|||
|
case LE:
|
|||
|
case GE:
|
|||
|
case GT:
|
|||
|
case LTU:
|
|||
|
case LEU:
|
|||
|
case GEU:
|
|||
|
case GTU:
|
|||
|
return 1;
|
|||
|
}
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
notice_update_cc (EXP, INSN)
|
|||
|
rtx EXP, INSN;
|
|||
|
{
|
|||
|
switch (GET_CODE (EXP))
|
|||
|
{
|
|||
|
case SET:
|
|||
|
switch (GET_CODE (SET_DEST (EXP)))
|
|||
|
{
|
|||
|
case CC0:
|
|||
|
cc_status.mdep = 0;
|
|||
|
cc_status.flags = 0;
|
|||
|
cc_status.value1 = 0;
|
|||
|
cc_status.value2 = SET_SRC (EXP);
|
|||
|
break;
|
|||
|
|
|||
|
case PC:
|
|||
|
break;
|
|||
|
|
|||
|
case REG:
|
|||
|
switch (GET_CODE (SET_SRC (EXP)))
|
|||
|
{
|
|||
|
case CALL:
|
|||
|
goto call;
|
|||
|
case MEM:
|
|||
|
if (GET_MODE (SET_SRC (EXP)) == QImode
|
|||
|
|| GET_MODE (SET_SRC (EXP)) == HImode)
|
|||
|
{
|
|||
|
cc_status.mdep = 0;
|
|||
|
cc_status.flags = CC_NO_OVERFLOW;
|
|||
|
cc_status.value1 = SET_DEST (EXP);
|
|||
|
cc_status.value2 = SET_SRC (EXP);
|
|||
|
break;
|
|||
|
}
|
|||
|
/* else: Fall through. */
|
|||
|
case CONST_INT:
|
|||
|
case SYMBOL_REF:
|
|||
|
case LABEL_REF:
|
|||
|
case CONST:
|
|||
|
case CONST_DOUBLE:
|
|||
|
case REG:
|
|||
|
if (cc_status.value1
|
|||
|
&& reg_overlap_mentioned_p (SET_DEST (EXP),
|
|||
|
cc_status.value1))
|
|||
|
cc_status.value1 = 0;
|
|||
|
if (cc_status.value2
|
|||
|
&& reg_overlap_mentioned_p (SET_DEST (EXP),
|
|||
|
cc_status.value2))
|
|||
|
cc_status.value2 = 0;
|
|||
|
break;
|
|||
|
|
|||
|
case UDIV:
|
|||
|
case UMOD:
|
|||
|
cc_status.mdep = CC_VALID_FOR_UNSIGNED;
|
|||
|
cc_status.flags = CC_NO_OVERFLOW;
|
|||
|
cc_status.value1 = SET_DEST (EXP);
|
|||
|
cc_status.value2 = SET_SRC (EXP);
|
|||
|
break;
|
|||
|
default:
|
|||
|
cc_status.mdep = 0;
|
|||
|
cc_status.flags = CC_NO_OVERFLOW;
|
|||
|
cc_status.value1 = SET_DEST (EXP);
|
|||
|
cc_status.value2 = SET_SRC (EXP);
|
|||
|
break;
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
case MEM:
|
|||
|
switch (GET_CODE (SET_SRC (EXP)))
|
|||
|
{
|
|||
|
case REG:
|
|||
|
if (GET_MODE (SET_SRC (EXP)) == QImode
|
|||
|
|| GET_MODE (SET_SRC (EXP)) == HImode)
|
|||
|
{
|
|||
|
cc_status.flags = CC_NO_OVERFLOW;
|
|||
|
cc_status.value1 = SET_DEST (EXP);
|
|||
|
cc_status.value2 = SET_SRC (EXP);
|
|||
|
cc_status.mdep = 0;
|
|||
|
break;
|
|||
|
}
|
|||
|
/* else: Fall through. */
|
|||
|
case CONST_INT:
|
|||
|
case SYMBOL_REF:
|
|||
|
case LABEL_REF:
|
|||
|
case CONST:
|
|||
|
case CONST_DOUBLE:
|
|||
|
case MEM:
|
|||
|
/* Need to forget cc_status about memory positions each
|
|||
|
time a memory store is made, even if the memory store
|
|||
|
insns in question doesn't modify the condition codes. */
|
|||
|
if (cc_status.value1 &&
|
|||
|
GET_CODE (cc_status.value1) == MEM)
|
|||
|
cc_status.value1 = 0;
|
|||
|
if (cc_status.value2 &&
|
|||
|
GET_CODE (cc_status.value2) == MEM)
|
|||
|
cc_status.value2 = 0;
|
|||
|
break;
|
|||
|
case SIGN_EXTEND:
|
|||
|
case FLOAT_EXTEND:
|
|||
|
case FLOAT_TRUNCATE:
|
|||
|
case FLOAT:
|
|||
|
case FIX:
|
|||
|
cc_status.flags = CC_NO_OVERFLOW;
|
|||
|
cc_status.value1 = SET_DEST (EXP);
|
|||
|
cc_status.value2 = SET_SRC (EXP);
|
|||
|
cc_status.mdep = 0;
|
|||
|
break;
|
|||
|
|
|||
|
default:
|
|||
|
abort ();
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
default:
|
|||
|
abort ();
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
case CALL:
|
|||
|
call:
|
|||
|
CC_STATUS_INIT;
|
|||
|
break;
|
|||
|
/* Do calls preserve the condition codes? (At least forget
|
|||
|
cc_status expressions if they refer to registers
|
|||
|
not preserved across calls. Also forget expressions
|
|||
|
about memory contents.) */
|
|||
|
if (cc_status.value1
|
|||
|
&& (refers_to_regno_p (PYR_TREG (0), PYR_TREG (15),
|
|||
|
cc_status.value1, 0)
|
|||
|
|| GET_CODE (cc_status.value1) == MEM))
|
|||
|
cc_status.value1 = 0;
|
|||
|
if (cc_status.value2
|
|||
|
&& (refers_to_regno_p (PYR_TREG (0), PYR_TREG (15),
|
|||
|
cc_status.value2, 0)
|
|||
|
|| GET_CODE (cc_status.value2) == MEM))
|
|||
|
cc_status.value2 = 0;
|
|||
|
break;
|
|||
|
|
|||
|
default:
|
|||
|
CC_STATUS_INIT;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
forget_cc_if_dependent (op)
|
|||
|
rtx op;
|
|||
|
{
|
|||
|
cc_status = cc_prev_status;
|
|||
|
if (cc_status.value1 && reg_overlap_mentioned_p (op, cc_status.value1))
|
|||
|
cc_status.value1 = 0;
|
|||
|
if (cc_status.value2 && reg_overlap_mentioned_p (op, cc_status.value2))
|
|||
|
cc_status.value2 = 0;
|
|||
|
}
|