d116300be8
* mn10300.c (notice_update_cc): Use CC_SET_ZN and CC_SET_ZNV. * mn10300.h (CC_NO_CARRY): Define. * mn10300.md: Use "set_zn" and "set_znv" to more accurately describe cc0 status. From-SVN: r14019
966 lines
24 KiB
C
966 lines
24 KiB
C
/* Subroutines for insn-output.c for Matsushita MN10300 series
|
||
Copyright (C) 1996, 1997 Free Software Foundation, Inc.
|
||
Contributed by Jeff Law (law@cygnus.com).
|
||
|
||
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 <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 "flags.h"
|
||
#include "recog.h"
|
||
#include "expr.h"
|
||
#include "tree.h"
|
||
#include "obstack.h"
|
||
|
||
/* Global registers known to hold the value zero.
|
||
|
||
Normally we'd depend on CSE and combine to put zero into a
|
||
register and re-use it.
|
||
|
||
However, on the mn10x00 processors we implicitly use the constant
|
||
zero in tst instructions, so we might be able to do better by
|
||
loading the value into a register in the prologue, then re-useing
|
||
that register throughout the function.
|
||
|
||
We could perform similar optimizations for other constants, but with
|
||
gcse due soon, it doesn't seem worth the effort.
|
||
|
||
These variables hold a rtx for a register known to hold the value
|
||
zero throughout the entire function, or NULL if no register of
|
||
the appropriate class has such a value throughout the life of the
|
||
function. */
|
||
rtx zero_dreg;
|
||
rtx zero_areg;
|
||
|
||
void
|
||
asm_file_start (file)
|
||
FILE *file;
|
||
{
|
||
fprintf (file, "#\tGCC For the Matsushita MN10300\n");
|
||
if (optimize)
|
||
fprintf (file, "# -O%d\n", optimize);
|
||
else
|
||
fprintf (file, "\n\n");
|
||
output_file_directive (file, main_input_filename);
|
||
}
|
||
|
||
|
||
/* Print operand X using operand code CODE to assembly language output file
|
||
FILE. */
|
||
|
||
void
|
||
print_operand (file, x, code)
|
||
FILE *file;
|
||
rtx x;
|
||
int code;
|
||
{
|
||
switch (code)
|
||
{
|
||
case 'b':
|
||
case 'B':
|
||
/* These are normal and reversed branches. */
|
||
switch (code == 'b' ? GET_CODE (x) : reverse_condition (GET_CODE (x)))
|
||
{
|
||
case NE:
|
||
fprintf (file, "ne");
|
||
break;
|
||
case EQ:
|
||
fprintf (file, "eq");
|
||
break;
|
||
case GE:
|
||
fprintf (file, "ge");
|
||
break;
|
||
case GT:
|
||
fprintf (file, "gt");
|
||
break;
|
||
case LE:
|
||
fprintf (file, "le");
|
||
break;
|
||
case LT:
|
||
fprintf (file, "lt");
|
||
break;
|
||
case GEU:
|
||
fprintf (file, "cc");
|
||
break;
|
||
case GTU:
|
||
fprintf (file, "hi");
|
||
break;
|
||
case LEU:
|
||
fprintf (file, "ls");
|
||
break;
|
||
case LTU:
|
||
fprintf (file, "cs");
|
||
break;
|
||
default:
|
||
abort ();
|
||
}
|
||
break;
|
||
case 'C':
|
||
/* This is used for the operand to a call instruction;
|
||
if it's a REG, enclose it in parens, else output
|
||
the operand normally. */
|
||
if (GET_CODE (x) == REG)
|
||
{
|
||
fputc ('(', file);
|
||
print_operand (file, x, 0);
|
||
fputc (')', file);
|
||
}
|
||
else
|
||
print_operand (file, x, 0);
|
||
break;
|
||
|
||
/* These are the least significant word in a 64bit value. */
|
||
case 'L':
|
||
switch (GET_CODE (x))
|
||
{
|
||
case MEM:
|
||
fputc ('(', file);
|
||
output_address (XEXP (x, 0));
|
||
fputc (')', file);
|
||
break;
|
||
|
||
case REG:
|
||
fprintf (file, "%s", reg_names[REGNO (x)]);
|
||
break;
|
||
|
||
case SUBREG:
|
||
fprintf (file, "%s",
|
||
reg_names[REGNO (SUBREG_REG (x)) + SUBREG_WORD (x)]);
|
||
break;
|
||
|
||
case CONST_DOUBLE:
|
||
{
|
||
long val[2];
|
||
REAL_VALUE_TYPE rv;
|
||
|
||
switch (GET_MODE (x))
|
||
{
|
||
case DFmode:
|
||
REAL_VALUE_FROM_CONST_DOUBLE (rv, x);
|
||
REAL_VALUE_TO_TARGET_DOUBLE (rv, val);
|
||
print_operand_address (file, GEN_INT (val[0]));
|
||
break;;
|
||
case SFmode:
|
||
REAL_VALUE_FROM_CONST_DOUBLE (rv, x);
|
||
REAL_VALUE_TO_TARGET_SINGLE (rv, val[0]);
|
||
print_operand_address (file, GEN_INT (val[0]));
|
||
break;;
|
||
case VOIDmode:
|
||
case DImode:
|
||
print_operand_address (file,
|
||
GEN_INT (CONST_DOUBLE_LOW (x)));
|
||
break;
|
||
}
|
||
break;
|
||
}
|
||
|
||
case CONST_INT:
|
||
print_operand_address (file, x);
|
||
break;
|
||
|
||
default:
|
||
abort ();
|
||
}
|
||
break;
|
||
|
||
/* Similarly, but for the most significant word. */
|
||
case 'H':
|
||
switch (GET_CODE (x))
|
||
{
|
||
case MEM:
|
||
fputc ('(', file);
|
||
x = adj_offsettable_operand (x, 4);
|
||
output_address (XEXP (x, 0));
|
||
fputc (')', file);
|
||
break;
|
||
|
||
case REG:
|
||
fprintf (file, "%s", reg_names[REGNO (x) + 1]);
|
||
break;
|
||
|
||
case SUBREG:
|
||
fprintf (file, "%s",
|
||
reg_names[REGNO (SUBREG_REG (x)) + SUBREG_WORD (x)] + 1);
|
||
break;
|
||
|
||
case CONST_DOUBLE:
|
||
{
|
||
long val[2];
|
||
REAL_VALUE_TYPE rv;
|
||
|
||
switch (GET_MODE (x))
|
||
{
|
||
case DFmode:
|
||
REAL_VALUE_FROM_CONST_DOUBLE (rv, x);
|
||
REAL_VALUE_TO_TARGET_DOUBLE (rv, val);
|
||
print_operand_address (file, GEN_INT (val[1]));
|
||
break;;
|
||
case SFmode:
|
||
abort ();
|
||
case VOIDmode:
|
||
case DImode:
|
||
print_operand_address (file,
|
||
GEN_INT (CONST_DOUBLE_HIGH (x)));
|
||
break;
|
||
}
|
||
break;
|
||
}
|
||
|
||
case CONST_INT:
|
||
if (INTVAL (x) < 0)
|
||
print_operand_address (file, GEN_INT (-1));
|
||
else
|
||
print_operand_address (file, GEN_INT (0));
|
||
break;
|
||
default:
|
||
abort ();
|
||
}
|
||
break;
|
||
|
||
case 'A':
|
||
fputc ('(', file);
|
||
if (GET_CODE (XEXP (x, 0)) == REG)
|
||
output_address (gen_rtx (PLUS, SImode, XEXP (x, 0), GEN_INT (0)));
|
||
else
|
||
output_address (XEXP (x, 0));
|
||
fputc (')', file);
|
||
break;
|
||
|
||
case 'N':
|
||
output_address (GEN_INT ((~INTVAL (x)) & 0xff));
|
||
break;
|
||
|
||
default:
|
||
switch (GET_CODE (x))
|
||
{
|
||
case MEM:
|
||
fputc ('(', file);
|
||
output_address (XEXP (x, 0));
|
||
fputc (')', file);
|
||
break;
|
||
|
||
case PLUS:
|
||
output_address (x);
|
||
break;
|
||
|
||
case REG:
|
||
fprintf (file, "%s", reg_names[REGNO (x)]);
|
||
break;
|
||
|
||
case SUBREG:
|
||
fprintf (file, "%s",
|
||
reg_names[REGNO (SUBREG_REG (x)) + SUBREG_WORD (x)]);
|
||
break;
|
||
|
||
/* This will only be single precision.... */
|
||
case CONST_DOUBLE:
|
||
{
|
||
unsigned long val;
|
||
REAL_VALUE_TYPE rv;
|
||
|
||
REAL_VALUE_FROM_CONST_DOUBLE (rv, x);
|
||
REAL_VALUE_TO_TARGET_SINGLE (rv, val);
|
||
print_operand_address (file, GEN_INT (val));
|
||
break;
|
||
}
|
||
|
||
case CONST_INT:
|
||
case SYMBOL_REF:
|
||
case CONST:
|
||
case LABEL_REF:
|
||
case CODE_LABEL:
|
||
print_operand_address (file, x);
|
||
break;
|
||
default:
|
||
abort ();
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* Output assembly language output for the address ADDR to FILE. */
|
||
|
||
void
|
||
print_operand_address (file, addr)
|
||
FILE *file;
|
||
rtx addr;
|
||
{
|
||
switch (GET_CODE (addr))
|
||
{
|
||
case REG:
|
||
if (addr == stack_pointer_rtx)
|
||
print_operand_address (file, gen_rtx (PLUS, SImode,
|
||
stack_pointer_rtx,
|
||
GEN_INT (0)));
|
||
else
|
||
print_operand (file, addr, 0);
|
||
break;
|
||
case PLUS:
|
||
{
|
||
rtx base, index;
|
||
if (REG_P (XEXP (addr, 0))
|
||
&& REG_OK_FOR_BASE_P (XEXP (addr, 0)))
|
||
base = XEXP (addr, 0), index = XEXP (addr, 1);
|
||
else if (REG_P (XEXP (addr, 1))
|
||
&& REG_OK_FOR_BASE_P (XEXP (addr, 1)))
|
||
base = XEXP (addr, 1), index = XEXP (addr, 0);
|
||
else
|
||
abort ();
|
||
print_operand (file, index, 0);
|
||
fputc (',', file);
|
||
print_operand (file, base, 0);;
|
||
break;
|
||
}
|
||
case SYMBOL_REF:
|
||
output_addr_const (file, addr);
|
||
break;
|
||
default:
|
||
output_addr_const (file, addr);
|
||
break;
|
||
}
|
||
}
|
||
|
||
int
|
||
can_use_return_insn ()
|
||
{
|
||
/* size includes the fixed stack space needed for function calls. */
|
||
int size = get_frame_size () + current_function_outgoing_args_size;
|
||
|
||
/* And space for the return pointer. */
|
||
size += current_function_outgoing_args_size ? 4 : 0;
|
||
|
||
return (reload_completed
|
||
&& size == 0
|
||
&& !regs_ever_live[2]
|
||
&& !regs_ever_live[3]
|
||
&& !regs_ever_live[6]
|
||
&& !regs_ever_live[7]
|
||
&& !frame_pointer_needed);
|
||
}
|
||
|
||
/* Count the number of tst insns which compare a data or address
|
||
register with zero. */
|
||
static void
|
||
count_tst_insns (dreg_countp, areg_countp)
|
||
int *dreg_countp;
|
||
int *areg_countp;
|
||
{
|
||
rtx insn;
|
||
|
||
/* Assume no tst insns exist. */
|
||
*dreg_countp = 0;
|
||
*areg_countp = 0;
|
||
|
||
/* If not optimizing, then quit now. */
|
||
if (!optimize)
|
||
return;
|
||
|
||
/* Walk through all the insns. */
|
||
for (insn = get_insns (); insn; insn = NEXT_INSN (insn))
|
||
{
|
||
rtx pat;
|
||
|
||
/* Ignore anything that is not a normal INSN. */
|
||
if (GET_CODE (insn) != INSN)
|
||
continue;
|
||
|
||
/* Ignore anything that isn't a SET. */
|
||
pat = PATTERN (insn);
|
||
if (GET_CODE (pat) != SET)
|
||
continue;
|
||
|
||
/* Check for a tst insn. */
|
||
if (SET_DEST (pat) == cc0_rtx
|
||
&& GET_CODE (SET_SRC (pat)) == REG)
|
||
{
|
||
if (REGNO_REG_CLASS (REGNO (SET_SRC (pat))) == DATA_REGS)
|
||
(*dreg_countp)++;
|
||
|
||
if (REGNO_REG_CLASS (REGNO (SET_SRC (pat))) == ADDRESS_REGS)
|
||
(*areg_countp)++;
|
||
}
|
||
|
||
/* Setting an address register to zero can also be optimized,
|
||
so count it just like a tst insn. */
|
||
if (GET_CODE (SET_DEST (pat)) == REG
|
||
&& GET_CODE (SET_SRC (pat)) == CONST_INT
|
||
&& INTVAL (SET_SRC (pat)) == 0
|
||
&& REGNO_REG_CLASS (REGNO (SET_DEST (pat))) == ADDRESS_REGS)
|
||
(*areg_countp)++;
|
||
}
|
||
}
|
||
|
||
void
|
||
expand_prologue ()
|
||
{
|
||
unsigned int size;
|
||
|
||
/* We need to end the current sequence so that count_tst_insns can
|
||
look at all the insns in this function. Normally this would be
|
||
unsafe, but it's OK in the prologue/epilogue expanders. */
|
||
end_sequence ();
|
||
|
||
/* Determine if it is profitable to put the value zero into a register
|
||
for the entire function. If so, set ZERO_DREG and ZERO_AREG. */
|
||
if (regs_ever_live[2] || regs_ever_live[3]
|
||
|| regs_ever_live[6] || regs_ever_live[7]
|
||
|| frame_pointer_needed)
|
||
{
|
||
int dreg_count, areg_count;
|
||
|
||
/* Get a count of the number of tst insns which use address and
|
||
data registers. */
|
||
count_tst_insns (&dreg_count, &areg_count);
|
||
|
||
/* If there's more than one tst insn using a data register, then
|
||
this optimization is a win. */
|
||
if (dreg_count > 1
|
||
&& (!regs_ever_live[2] || !regs_ever_live[3]))
|
||
{
|
||
if (!regs_ever_live[2])
|
||
{
|
||
regs_ever_live[2] = 1;
|
||
zero_dreg = gen_rtx (REG, SImode, 2);
|
||
}
|
||
else
|
||
{
|
||
regs_ever_live[3] = 1;
|
||
zero_dreg = gen_rtx (REG, SImode, 3);
|
||
}
|
||
}
|
||
else
|
||
zero_dreg = NULL_RTX;
|
||
|
||
/* If there's more than two tst insns using an address register,
|
||
then this optimization is a win. */
|
||
if (areg_count > 2
|
||
&& (!regs_ever_live[6] || !regs_ever_live[7]))
|
||
{
|
||
if (!regs_ever_live[6])
|
||
{
|
||
regs_ever_live[6] = 1;
|
||
zero_areg = gen_rtx (REG, SImode, 6);
|
||
}
|
||
else
|
||
{
|
||
regs_ever_live[7] = 1;
|
||
zero_areg = gen_rtx (REG, SImode, 7);
|
||
}
|
||
}
|
||
else
|
||
zero_areg = NULL_RTX;
|
||
}
|
||
else
|
||
{
|
||
zero_dreg = NULL_RTX;
|
||
zero_areg = NULL_RTX;
|
||
}
|
||
|
||
/* Start a new sequence. */
|
||
start_sequence ();
|
||
|
||
/* SIZE includes the fixed stack space needed for function calls. */
|
||
size = get_frame_size () + current_function_outgoing_args_size;
|
||
size += (current_function_outgoing_args_size ? 4 : 0);
|
||
|
||
/* If this is an old-style varargs function, then its arguments
|
||
need to be flushed back to the stack. */
|
||
if (current_function_varargs)
|
||
{
|
||
emit_move_insn (gen_rtx (MEM, SImode,
|
||
gen_rtx (PLUS, Pmode, stack_pointer_rtx,
|
||
GEN_INT (4))),
|
||
gen_rtx (REG, SImode, 0));
|
||
emit_move_insn (gen_rtx (MEM, SImode,
|
||
gen_rtx (PLUS, Pmode, stack_pointer_rtx,
|
||
GEN_INT (8))),
|
||
gen_rtx (REG, SImode, 1));
|
||
}
|
||
|
||
/* And now store all the registers onto the stack with a
|
||
single two byte instruction. */
|
||
if (regs_ever_live[2] || regs_ever_live[3]
|
||
|| regs_ever_live[6] || regs_ever_live[7]
|
||
|| frame_pointer_needed)
|
||
emit_insn (gen_store_movm ());
|
||
|
||
/* Now put the frame pointer into the frame pointer register. */
|
||
if (frame_pointer_needed)
|
||
emit_move_insn (frame_pointer_rtx, stack_pointer_rtx);
|
||
|
||
/* Allocate stack for this frame. */
|
||
if (size)
|
||
emit_insn (gen_addsi3 (stack_pointer_rtx,
|
||
stack_pointer_rtx,
|
||
GEN_INT (-size)));
|
||
|
||
/* Load zeros into registers as needed. */
|
||
if (zero_dreg)
|
||
emit_move_insn (zero_dreg, const0_rtx);
|
||
|
||
if (zero_areg)
|
||
emit_move_insn (zero_areg, const0_rtx);
|
||
}
|
||
|
||
void
|
||
expand_epilogue ()
|
||
{
|
||
unsigned int size;
|
||
|
||
/* SIZE includes the fixed stack space needed for function calls. */
|
||
size = get_frame_size () + current_function_outgoing_args_size;
|
||
size += (current_function_outgoing_args_size ? 4 : 0);
|
||
|
||
/* Cut back the stack. */
|
||
if (frame_pointer_needed)
|
||
{
|
||
emit_move_insn (stack_pointer_rtx, frame_pointer_rtx);
|
||
size = 0;
|
||
}
|
||
else if ((regs_ever_live[2] || regs_ever_live[3]
|
||
|| regs_ever_live[6] || regs_ever_live[7])
|
||
&& size > 255)
|
||
{
|
||
emit_insn (gen_addsi3 (stack_pointer_rtx,
|
||
stack_pointer_rtx,
|
||
GEN_INT (size)));
|
||
size = 0;
|
||
}
|
||
|
||
/* For simplicity, we just movm all the callee saved registers to
|
||
the stack with one instruction.
|
||
|
||
?!? Only save registers which are actually used. Reduces
|
||
stack requireents and is faster. */
|
||
if (regs_ever_live[2] || regs_ever_live[3]
|
||
|| regs_ever_live[6] || regs_ever_live[7]
|
||
|| frame_pointer_needed)
|
||
emit_jump_insn (gen_return_internal_regs (GEN_INT (size)));
|
||
else
|
||
{
|
||
if (size)
|
||
{
|
||
emit_insn (gen_addsi3 (stack_pointer_rtx,
|
||
stack_pointer_rtx,
|
||
GEN_INT (size)));
|
||
emit_jump_insn (gen_return_internal ());
|
||
}
|
||
else
|
||
{
|
||
emit_jump_insn (gen_return ());
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Update the condition code from the insn. */
|
||
|
||
void
|
||
notice_update_cc (body, insn)
|
||
rtx body;
|
||
rtx insn;
|
||
{
|
||
switch (get_attr_cc (insn))
|
||
{
|
||
case CC_NONE:
|
||
/* Insn does not affect CC at all. */
|
||
break;
|
||
|
||
case CC_NONE_0HIT:
|
||
/* Insn does not change CC, but the 0'th operand has been changed. */
|
||
if (cc_status.value1 != 0
|
||
&& reg_overlap_mentioned_p (recog_operand[0], cc_status.value1))
|
||
cc_status.value1 = 0;
|
||
break;
|
||
|
||
case CC_SET_ZN:
|
||
/* Insn sets the Z,N flags of CC to recog_operand[0].
|
||
V,C are unusable. */
|
||
CC_STATUS_INIT;
|
||
cc_status.flags |= CC_NO_CARRY | CC_OVERFLOW_UNUSABLE;
|
||
cc_status.value1 = recog_operand[0];
|
||
break;
|
||
|
||
case CC_SET_ZNV:
|
||
/* Insn sets the Z,N,V flags of CC to recog_operand[0].
|
||
C is unusable. */
|
||
CC_STATUS_INIT;
|
||
cc_status.flags |= CC_NO_CARRY;
|
||
cc_status.value1 = recog_operand[0];
|
||
break;
|
||
|
||
case CC_COMPARE:
|
||
/* The insn is a compare instruction. */
|
||
CC_STATUS_INIT;
|
||
cc_status.value1 = SET_SRC (body);
|
||
break;
|
||
|
||
case CC_INVERT:
|
||
/* The insn is a compare instruction. */
|
||
CC_STATUS_INIT;
|
||
cc_status.value1 = SET_SRC (body);
|
||
cc_status.flags |= CC_INVERTED;
|
||
break;
|
||
|
||
case CC_CLOBBER:
|
||
/* Insn doesn't leave CC in a usable state. */
|
||
CC_STATUS_INIT;
|
||
break;
|
||
|
||
default:
|
||
abort ();
|
||
}
|
||
}
|
||
|
||
/* Return true if OP is a valid call operand. */
|
||
|
||
int
|
||
call_address_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
return (GET_CODE (op) == SYMBOL_REF || GET_CODE (op) == REG);
|
||
}
|
||
|
||
/* What (if any) secondary registers are needed to move IN with mode
|
||
MODE into a register from in register class CLASS.
|
||
|
||
We might be able to simplify this. */
|
||
enum reg_class
|
||
secondary_reload_class (class, mode, in)
|
||
enum reg_class class;
|
||
enum machine_mode mode;
|
||
rtx in;
|
||
{
|
||
int regno;
|
||
|
||
/* Memory loads less than a full word wide can't have an
|
||
address or stack pointer destination. They must use
|
||
a data register as an intermediate register. */
|
||
if (GET_CODE (in) == MEM
|
||
&& (mode == QImode || mode == HImode)
|
||
&& (class == ADDRESS_REGS || class == SP_REGS))
|
||
return DATA_REGS;
|
||
|
||
/* We can't directly load sp + const_int into a data register;
|
||
we must use an address register as an intermediate. */
|
||
if (class != SP_REGS
|
||
&& class != ADDRESS_REGS
|
||
&& class != SP_OR_ADDRESS_REGS
|
||
&& (in == stack_pointer_rtx
|
||
|| (GET_CODE (in) == PLUS
|
||
&& (XEXP (in, 0) == stack_pointer_rtx
|
||
|| XEXP (in, 1) == stack_pointer_rtx))))
|
||
return ADDRESS_REGS;
|
||
|
||
if (GET_CODE (in) == PLUS
|
||
&& (XEXP (in, 0) == stack_pointer_rtx
|
||
|| XEXP (in, 1) == stack_pointer_rtx))
|
||
return DATA_REGS;
|
||
|
||
|
||
/* Otherwise assume no secondary reloads are needed. */
|
||
return NO_REGS;
|
||
}
|
||
|
||
int
|
||
initial_offset (from, to)
|
||
int from, to;
|
||
{
|
||
/* The difference between the argument pointer and the frame pointer
|
||
is the size of the callee register save area. */
|
||
if (from == ARG_POINTER_REGNUM && to == FRAME_POINTER_REGNUM)
|
||
{
|
||
if (regs_ever_live[2] || regs_ever_live[3]
|
||
|| regs_ever_live[6] || regs_ever_live[7]
|
||
|| frame_pointer_needed)
|
||
return 16;
|
||
else
|
||
return 0;
|
||
}
|
||
|
||
/* The difference between the argument pointer and the stack pointer is
|
||
the sum of the size of this function's frame, the callee register save
|
||
area, and the fixed stack space needed for function calls (if any). */
|
||
if (from == ARG_POINTER_REGNUM && to == STACK_POINTER_REGNUM)
|
||
{
|
||
if (regs_ever_live[2] || regs_ever_live[3]
|
||
|| regs_ever_live[6] || regs_ever_live[7]
|
||
|| frame_pointer_needed)
|
||
return (get_frame_size () + 16
|
||
+ (current_function_outgoing_args_size
|
||
? current_function_outgoing_args_size + 4 : 0));
|
||
else
|
||
return (get_frame_size ()
|
||
+ (current_function_outgoing_args_size
|
||
? current_function_outgoing_args_size + 4 : 0));
|
||
}
|
||
|
||
/* The difference between the frame pointer and stack pointer is the sum
|
||
of the size of this function's frame and the fixed stack space needed
|
||
for function calls (if any). */
|
||
if (from == FRAME_POINTER_REGNUM && to == STACK_POINTER_REGNUM)
|
||
return (get_frame_size ()
|
||
+ (current_function_outgoing_args_size
|
||
? current_function_outgoing_args_size + 4 : 0));
|
||
|
||
abort ();
|
||
}
|
||
|
||
/* Flush the argument registers to the stack for a stdarg function;
|
||
return the new argument pointer. */
|
||
rtx
|
||
mn10300_builtin_saveregs (arglist)
|
||
tree arglist;
|
||
{
|
||
rtx offset;
|
||
tree fntype = TREE_TYPE (current_function_decl);
|
||
int argadj = ((!(TYPE_ARG_TYPES (fntype) != 0
|
||
&& (TREE_VALUE (tree_last (TYPE_ARG_TYPES (fntype)))
|
||
!= void_type_node)))
|
||
? UNITS_PER_WORD : 0);
|
||
|
||
if (argadj)
|
||
offset = plus_constant (current_function_arg_offset_rtx, argadj);
|
||
else
|
||
offset = current_function_arg_offset_rtx;
|
||
|
||
emit_move_insn (gen_rtx (MEM, SImode, current_function_internal_arg_pointer),
|
||
gen_rtx (REG, SImode, 0));
|
||
emit_move_insn (gen_rtx (MEM, SImode,
|
||
plus_constant
|
||
(current_function_internal_arg_pointer, 4)),
|
||
gen_rtx (REG, SImode, 1));
|
||
return copy_to_reg (expand_binop (Pmode, add_optab,
|
||
current_function_internal_arg_pointer,
|
||
offset, 0, 0, OPTAB_LIB_WIDEN));
|
||
}
|
||
|
||
/* Return an RTX to represent where a value with mode MODE will be returned
|
||
from a function. If the result is 0, the argument is pushed. */
|
||
|
||
rtx
|
||
function_arg (cum, mode, type, named)
|
||
CUMULATIVE_ARGS *cum;
|
||
enum machine_mode mode;
|
||
tree type;
|
||
int named;
|
||
{
|
||
rtx result = 0;
|
||
int size, align;
|
||
|
||
/* We only support using 2 data registers as argument registers. */
|
||
int nregs = 2;
|
||
|
||
/* Figure out the size of the object to be passed. */
|
||
if (mode == BLKmode)
|
||
size = int_size_in_bytes (type);
|
||
else
|
||
size = GET_MODE_SIZE (mode);
|
||
|
||
/* Figure out the alignment of the object to be passed. */
|
||
align = size;
|
||
|
||
cum->nbytes = (cum->nbytes + 3) & ~3;
|
||
|
||
/* Don't pass this arg via a register if all the argument registers
|
||
are used up. */
|
||
if (cum->nbytes > nregs * UNITS_PER_WORD)
|
||
return 0;
|
||
|
||
/* Don't pass this arg via a register if it would be split between
|
||
registers and memory. */
|
||
if (type == NULL_TREE
|
||
&& cum->nbytes + size > nregs * UNITS_PER_WORD)
|
||
return 0;
|
||
|
||
switch (cum->nbytes / UNITS_PER_WORD)
|
||
{
|
||
case 0:
|
||
result = gen_rtx (REG, mode, 0);
|
||
break;
|
||
case 1:
|
||
result = gen_rtx (REG, mode, 1);
|
||
break;
|
||
default:
|
||
result = 0;
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/* Return the number of registers to use for an argument passed partially
|
||
in registers and partially in memory. */
|
||
|
||
int
|
||
function_arg_partial_nregs (cum, mode, type, named)
|
||
CUMULATIVE_ARGS *cum;
|
||
enum machine_mode mode;
|
||
tree type;
|
||
int named;
|
||
{
|
||
int size, align;
|
||
|
||
/* We only support using 2 data registers as argument registers. */
|
||
int nregs = 2;
|
||
|
||
/* Figure out the size of the object to be passed. */
|
||
if (mode == BLKmode)
|
||
size = int_size_in_bytes (type);
|
||
else
|
||
size = GET_MODE_SIZE (mode);
|
||
|
||
/* Figure out the alignment of the object to be passed. */
|
||
align = size;
|
||
|
||
cum->nbytes = (cum->nbytes + 3) & ~3;
|
||
|
||
/* Don't pass this arg via a register if all the argument registers
|
||
are used up. */
|
||
if (cum->nbytes > nregs * UNITS_PER_WORD)
|
||
return 0;
|
||
|
||
if (cum->nbytes + size <= nregs * UNITS_PER_WORD)
|
||
return 0;
|
||
|
||
/* Don't pass this arg via a register if it would be split between
|
||
registers and memory. */
|
||
if (type == NULL_TREE
|
||
&& cum->nbytes + size > nregs * UNITS_PER_WORD)
|
||
return 0;
|
||
|
||
return (nregs * UNITS_PER_WORD - cum->nbytes) / UNITS_PER_WORD;
|
||
}
|
||
|
||
/* Output a tst insn. */
|
||
char *
|
||
output_tst (operand, insn)
|
||
rtx operand, insn;
|
||
{
|
||
rtx temp;
|
||
int past_call = 0;
|
||
|
||
/* If we have a data register which is known to be zero throughout
|
||
the function, then use it instead of doing a search. */
|
||
if (zero_dreg && REGNO_REG_CLASS (REGNO (operand)) == DATA_REGS)
|
||
{
|
||
rtx xoperands[2];
|
||
xoperands[0] = operand;
|
||
xoperands[1] = zero_dreg;
|
||
|
||
output_asm_insn ("cmp %1,%0", xoperands);
|
||
return "";
|
||
}
|
||
|
||
/* Similarly for address registers. */
|
||
if (zero_areg && REGNO_REG_CLASS (REGNO (operand)) == ADDRESS_REGS)
|
||
{
|
||
rtx xoperands[2];
|
||
xoperands[0] = operand;
|
||
xoperands[1] = zero_areg;
|
||
|
||
output_asm_insn ("cmp %1,%0", xoperands);
|
||
return "";
|
||
}
|
||
|
||
/* We can save a byte if we can find a register which has the value
|
||
zero in it. */
|
||
temp = PREV_INSN (insn);
|
||
while (optimize && temp)
|
||
{
|
||
rtx set;
|
||
|
||
/* We allow the search to go through call insns. We record
|
||
the fact that we've past a CALL_INSN and reject matches which
|
||
use call clobbered registers. */
|
||
if (GET_CODE (temp) == CODE_LABEL
|
||
|| GET_CODE (temp) == JUMP_INSN
|
||
|| GET_CODE (temp) == BARRIER)
|
||
break;
|
||
|
||
if (GET_CODE (temp) == CALL_INSN)
|
||
past_call = 1;
|
||
|
||
if (GET_CODE (temp) == NOTE)
|
||
{
|
||
temp = PREV_INSN (temp);
|
||
continue;
|
||
}
|
||
|
||
/* It must be an insn, see if it is a simple set. */
|
||
set = single_set (temp);
|
||
if (!set)
|
||
{
|
||
temp = PREV_INSN (temp);
|
||
continue;
|
||
}
|
||
|
||
/* Are we setting a data register to zero (this does not win for
|
||
address registers)?
|
||
|
||
If it's a call clobbered register, have we past a call?
|
||
|
||
Make sure the register we find isn't the same as ourself;
|
||
the mn10300 can't encode that. */
|
||
if (REG_P (SET_DEST (set))
|
||
&& SET_SRC (set) == CONST0_RTX (GET_MODE (SET_DEST (set)))
|
||
&& !reg_set_between_p (SET_DEST (set), temp, insn)
|
||
&& (REGNO_REG_CLASS (REGNO (SET_DEST (set)))
|
||
== REGNO_REG_CLASS (REGNO (operand)))
|
||
&& REGNO (SET_DEST (set)) != REGNO (operand)
|
||
&& (!past_call
|
||
|| !call_used_regs[REGNO (SET_DEST (set))]))
|
||
{
|
||
rtx xoperands[2];
|
||
xoperands[0] = operand;
|
||
xoperands[1] = SET_DEST (set);
|
||
|
||
output_asm_insn ("cmp %1,%0", xoperands);
|
||
return "";
|
||
}
|
||
temp = PREV_INSN (temp);
|
||
}
|
||
return "cmp 0,%0";
|
||
}
|
||
|
||
int
|
||
impossible_plus_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
extern rtx *reg_equiv_mem;
|
||
rtx reg1, reg2;
|
||
|
||
if (GET_CODE (op) != PLUS)
|
||
return 0;
|
||
|
||
if (XEXP (op, 0) == stack_pointer_rtx
|
||
|| XEXP (op, 1) == stack_pointer_rtx)
|
||
return 1;
|
||
|
||
return 0;
|
||
}
|