1533 lines
40 KiB
C
1533 lines
40 KiB
C
|
/* Subroutines for insn-output.c for Matsushita MN10200 series
|
|||
|
Copyright (C) 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;
|
|||
|
|
|||
|
/* Note whether or not we need an out of line epilogue. */
|
|||
|
static int out_of_line_epilogue;
|
|||
|
|
|||
|
/* Indicate this file was compiled by gcc and what optimization
|
|||
|
level was used. */
|
|||
|
void
|
|||
|
asm_file_start (file)
|
|||
|
FILE *file;
|
|||
|
{
|
|||
|
fprintf (file, "#\tGCC For the Matsushita MN10200\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 32bit value.
|
|||
|
'o' allows us to sign extend a constant if doing so
|
|||
|
makes for more compact code. */
|
|||
|
case 'L':
|
|||
|
case 'o':
|
|||
|
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:
|
|||
|
if (code == 'L')
|
|||
|
{
|
|||
|
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 & 0xffff));
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
long val;
|
|||
|
REAL_VALUE_TYPE rv;
|
|||
|
|
|||
|
REAL_VALUE_FROM_CONST_DOUBLE (rv, x);
|
|||
|
REAL_VALUE_TO_TARGET_SINGLE (rv, val);
|
|||
|
|
|||
|
val &= 0xffff;
|
|||
|
val = (((val) & 0xffff) ^ (~0x7fff)) + 0x8000;
|
|||
|
print_operand_address (file, GEN_INT (val));
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
case CONST_INT:
|
|||
|
if (code == 'L')
|
|||
|
print_operand_address (file, GEN_INT ((INTVAL (x) & 0xffff)));
|
|||
|
else
|
|||
|
{
|
|||
|
unsigned int val = INTVAL (x) & 0xffff;
|
|||
|
val = (((val) & 0xffff) ^ (~0x7fff)) + 0x8000;
|
|||
|
print_operand_address (file, GEN_INT (val));
|
|||
|
}
|
|||
|
break;
|
|||
|
default:
|
|||
|
abort ();
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
/* Similarly, but for the most significant word. */
|
|||
|
case 'H':
|
|||
|
case 'h':
|
|||
|
switch (GET_CODE (x))
|
|||
|
{
|
|||
|
case MEM:
|
|||
|
fputc ('(', file);
|
|||
|
x = adj_offsettable_operand (x, 2);
|
|||
|
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:
|
|||
|
if (code == 'H')
|
|||
|
{
|
|||
|
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 >> 16) & 0xffff));
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
long val;
|
|||
|
REAL_VALUE_TYPE rv;
|
|||
|
|
|||
|
REAL_VALUE_FROM_CONST_DOUBLE (rv, x);
|
|||
|
REAL_VALUE_TO_TARGET_SINGLE (rv, val);
|
|||
|
|
|||
|
val = (val >> 16) & 0xffff;
|
|||
|
val = (((val) & 0xffff) ^ (~0x7fff)) + 0x8000;
|
|||
|
|
|||
|
print_operand_address (file, GEN_INT (val));
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
case CONST_INT:
|
|||
|
if (code == 'H')
|
|||
|
print_operand_address (file,
|
|||
|
GEN_INT ((INTVAL (x) >> 16) & 0xffff));
|
|||
|
else
|
|||
|
{
|
|||
|
unsigned int val = (INTVAL (x) >> 16) & 0xffff;
|
|||
|
val = (((val) & 0xffff) ^ (~0x7fff)) + 0x8000;
|
|||
|
|
|||
|
print_operand_address (file, GEN_INT (val));
|
|||
|
}
|
|||
|
break;
|
|||
|
default:
|
|||
|
abort ();
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
/* Output ~CONST_INT. */
|
|||
|
case 'N':
|
|||
|
if (GET_CODE (x) != CONST_INT)
|
|||
|
abort ();
|
|||
|
fprintf (file, "%d", ~INTVAL (x));
|
|||
|
break;
|
|||
|
|
|||
|
/* An address which can not be register indirect, if it is
|
|||
|
register indirect, then turn it into reg + disp. */
|
|||
|
case 'A':
|
|||
|
if (GET_CODE (x) != MEM)
|
|||
|
abort ();
|
|||
|
if (GET_CODE (XEXP (x, 0)) == REG)
|
|||
|
x = gen_rtx (PLUS, PSImode, XEXP (x, 0), GEN_INT (0));
|
|||
|
else
|
|||
|
x = XEXP (x, 0);
|
|||
|
fputc ('(', file);
|
|||
|
output_address (x);
|
|||
|
fputc (')', file);
|
|||
|
break;
|
|||
|
|
|||
|
case 'Z':
|
|||
|
print_operand (file, XEXP (x, 1), 0);
|
|||
|
break;
|
|||
|
|
|||
|
/* More cases where we can sign-extend a CONST_INT if it
|
|||
|
results in more compact code. */
|
|||
|
case 's':
|
|||
|
case 'S':
|
|||
|
if (GET_CODE (x) == CONST_INT)
|
|||
|
{
|
|||
|
int val = INTVAL (x);
|
|||
|
|
|||
|
if (code == 's')
|
|||
|
x = GEN_INT (((val & 0xffff) ^ (~0x7fff)) + 0x8000);
|
|||
|
else
|
|||
|
x = GEN_INT (((val & 0xff) ^ (~0x7f)) + 0x80);
|
|||
|
}
|
|||
|
/* FALL THROUGH */
|
|||
|
default:
|
|||
|
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_INT:
|
|||
|
case CONST_DOUBLE:
|
|||
|
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:
|
|||
|
print_operand (file, addr, 0);
|
|||
|
break;
|
|||
|
case PLUS:
|
|||
|
{
|
|||
|
rtx base, index;
|
|||
|
/* The base and index could be in any order, so we have
|
|||
|
to figure out which is the base and which is the index.
|
|||
|
Uses the same code as GO_IF_LEGITIMATE_ADDRESS. */
|
|||
|
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;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* Count the number of tst insns which compare an address register
|
|||
|
with zero. */
|
|||
|
static void
|
|||
|
count_tst_insns (areg_countp)
|
|||
|
int *areg_countp;
|
|||
|
{
|
|||
|
rtx insn;
|
|||
|
|
|||
|
/* Assume no tst insns exist. */
|
|||
|
*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
|
|||
|
&& REGNO_REG_CLASS (REGNO (SET_SRC (pat))) == ADDRESS_REGS)
|
|||
|
(*areg_countp)++;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* Return the total size (in bytes) of the current function's frame.
|
|||
|
This is the size of the register save area + the size of locals,
|
|||
|
spills, etc. */
|
|||
|
int
|
|||
|
total_frame_size ()
|
|||
|
{
|
|||
|
unsigned int size = get_frame_size ();
|
|||
|
unsigned int outgoing_args_size = current_function_outgoing_args_size;
|
|||
|
int i;
|
|||
|
|
|||
|
/* First figure out if we're going to use an out of line
|
|||
|
prologue, if so we have to make space for all the
|
|||
|
registers, even if we don't use them. */
|
|||
|
if (optimize && !current_function_needs_context && !frame_pointer_needed)
|
|||
|
{
|
|||
|
int inline_count, outline_count;
|
|||
|
|
|||
|
/* Compute how many bytes an inline prologue would take.
|
|||
|
|
|||
|
Each address register store takes two bytes, each data register
|
|||
|
store takes three bytes. */
|
|||
|
inline_count = 0;
|
|||
|
if (regs_ever_live[5])
|
|||
|
inline_count += 2;
|
|||
|
if (regs_ever_live[6])
|
|||
|
inline_count += 2;
|
|||
|
if (regs_ever_live[2])
|
|||
|
inline_count += 3;
|
|||
|
if (regs_ever_live[3])
|
|||
|
inline_count += 3;
|
|||
|
|
|||
|
/* If this function has any stack, then the stack adjustment
|
|||
|
will take two (or more) bytes. */
|
|||
|
if (size || outgoing_args_size
|
|||
|
|| regs_ever_live[5] || regs_ever_live[6]
|
|||
|
|| regs_ever_live[2] || regs_ever_live[3])
|
|||
|
inline_count += 2;
|
|||
|
|
|||
|
/* Multiply the current count by two and add one to account for the
|
|||
|
epilogue insns. */
|
|||
|
inline_count = inline_count * 2 + 1;
|
|||
|
|
|||
|
/* Now compute how many bytes an out of line sequence would take. */
|
|||
|
/* A relaxed jsr will be three bytes. */
|
|||
|
outline_count = 3;
|
|||
|
|
|||
|
/* If there are outgoing arguments, then we will need a stack
|
|||
|
pointer adjustment after the call to the prologue, two
|
|||
|
more bytes. */
|
|||
|
outline_count += (outgoing_args_size == 0 ? 0 : 2);
|
|||
|
|
|||
|
/* If there is some local frame to allocate, it will need to be
|
|||
|
done before the call to the prologue, two more bytes. */
|
|||
|
if (get_frame_size () != 0)
|
|||
|
outline_count += 2;
|
|||
|
|
|||
|
/* Now account for the epilogue, multiply the base count by two,
|
|||
|
then deal with optimizing away the rts instruction. */
|
|||
|
outline_count = outline_count * 2 + 1;
|
|||
|
|
|||
|
if (get_frame_size () == 0 && outgoing_args_size == 0)
|
|||
|
outline_count -= 1;
|
|||
|
|
|||
|
/* If an out of line prologue is smaller, use it. */
|
|||
|
if (inline_count > outline_count)
|
|||
|
return size + outgoing_args_size + 16;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
|
|||
|
{
|
|||
|
if (regs_ever_live[i] && !call_used_regs[i] && ! fixed_regs[i]
|
|||
|
|| (i == FRAME_POINTER_REGNUM && frame_pointer_needed))
|
|||
|
size += 4;
|
|||
|
}
|
|||
|
|
|||
|
return (size + outgoing_args_size);
|
|||
|
}
|
|||
|
|
|||
|
/* Expand the prologue into RTL. */
|
|||
|
void
|
|||
|
expand_prologue ()
|
|||
|
{
|
|||
|
unsigned int size = total_frame_size ();
|
|||
|
unsigned int outgoing_args_size = current_function_outgoing_args_size;
|
|||
|
int offset, i;
|
|||
|
|
|||
|
zero_areg = NULL_RTX;
|
|||
|
zero_dreg = NULL_RTX;
|
|||
|
|
|||
|
/* If optimizing, see if we should do an out of line prologue/epilogue
|
|||
|
sequence.
|
|||
|
|
|||
|
We don't support out of line prologues if the current function
|
|||
|
needs a context or frame pointer. */
|
|||
|
if (optimize && !current_function_needs_context && !frame_pointer_needed)
|
|||
|
{
|
|||
|
int inline_count, outline_count, areg_count;
|
|||
|
|
|||
|
/* 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 ();
|
|||
|
|
|||
|
/* Get a count of the number of tst insns which use address
|
|||
|
registers (it's not profitable to try and improve tst insns
|
|||
|
which use data registers). */
|
|||
|
count_tst_insns (&areg_count);
|
|||
|
|
|||
|
/* Now start a new sequence. */
|
|||
|
start_sequence ();
|
|||
|
|
|||
|
/* Compute how many bytes an inline prologue would take.
|
|||
|
|
|||
|
Each address register store takes two bytes, each data register
|
|||
|
store takes three bytes. */
|
|||
|
inline_count = 0;
|
|||
|
if (regs_ever_live[5])
|
|||
|
inline_count += 2;
|
|||
|
if (regs_ever_live[6])
|
|||
|
inline_count += 2;
|
|||
|
if (regs_ever_live[2])
|
|||
|
inline_count += 3;
|
|||
|
if (regs_ever_live[3])
|
|||
|
inline_count += 3;
|
|||
|
|
|||
|
/* If this function has any stack, then the stack adjustment
|
|||
|
will take two (or more) bytes. */
|
|||
|
if (size || outgoing_args_size
|
|||
|
|| regs_ever_live[5] || regs_ever_live[6]
|
|||
|
|| regs_ever_live[2] || regs_ever_live[3])
|
|||
|
inline_count += 2;
|
|||
|
|
|||
|
/* Multiply the current count by two and add one to account for the
|
|||
|
epilogue insns. */
|
|||
|
inline_count = inline_count * 2 + 1;
|
|||
|
|
|||
|
/* Now compute how many bytes an out of line sequence would take. */
|
|||
|
/* A relaxed jsr will be three bytes. */
|
|||
|
outline_count = 3;
|
|||
|
|
|||
|
/* If there are outgoing arguments, then we will need a stack
|
|||
|
pointer adjustment after the call to the prologue, two
|
|||
|
more bytes. */
|
|||
|
outline_count += (outgoing_args_size == 0 ? 0 : 2);
|
|||
|
|
|||
|
/* If there is some local frame to allocate, it will need to be
|
|||
|
done before the call to the prologue, two more bytes. */
|
|||
|
if (get_frame_size () != 0)
|
|||
|
outline_count += 2;
|
|||
|
|
|||
|
/* Now account for the epilogue, multiply the base count by two,
|
|||
|
then deal with optimizing away the rts instruction. */
|
|||
|
outline_count = outline_count * 2 + 1;
|
|||
|
|
|||
|
if (get_frame_size () == 0 && outgoing_args_size == 0)
|
|||
|
outline_count -= 1;
|
|||
|
|
|||
|
/* If an out of line prologue is smaller, use it. */
|
|||
|
if (inline_count > outline_count)
|
|||
|
{
|
|||
|
if (get_frame_size () != 0)
|
|||
|
emit_insn (gen_addpsi3 (stack_pointer_rtx, stack_pointer_rtx,
|
|||
|
GEN_INT (-size + outgoing_args_size + 16)));
|
|||
|
emit_insn (gen_outline_prologue_call ());
|
|||
|
|
|||
|
if (outgoing_args_size)
|
|||
|
emit_insn (gen_addpsi3 (stack_pointer_rtx, stack_pointer_rtx,
|
|||
|
GEN_INT (-outgoing_args_size)));
|
|||
|
|
|||
|
out_of_line_epilogue = 1;
|
|||
|
|
|||
|
/* 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. */
|
|||
|
|
|||
|
/* First see if we could load the value into a data register
|
|||
|
since that's the most efficient way. */
|
|||
|
if (areg_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, HImode, 2);
|
|||
|
}
|
|||
|
if (!regs_ever_live[3])
|
|||
|
{
|
|||
|
regs_ever_live[3] = 1;
|
|||
|
zero_dreg = gen_rtx (REG, HImode, 3);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* Now see if we could load the value into a address register. */
|
|||
|
if (zero_dreg == NULL_RTX
|
|||
|
&& areg_count > 2
|
|||
|
&& (!regs_ever_live[5] || !regs_ever_live[6]))
|
|||
|
{
|
|||
|
if (!regs_ever_live[5])
|
|||
|
{
|
|||
|
regs_ever_live[5] = 1;
|
|||
|
zero_areg = gen_rtx (REG, HImode, 5);
|
|||
|
}
|
|||
|
if (!regs_ever_live[6])
|
|||
|
{
|
|||
|
regs_ever_live[6] = 1;
|
|||
|
zero_areg = gen_rtx (REG, HImode, 6);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (zero_dreg)
|
|||
|
emit_move_insn (zero_dreg, const0_rtx);
|
|||
|
|
|||
|
if (zero_areg)
|
|||
|
emit_move_insn (zero_areg, const0_rtx);
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
out_of_line_epilogue = 0;
|
|||
|
|
|||
|
/* Temporarily stuff the static chain onto the stack so we can
|
|||
|
use a0 as a scratch register during the prologue. */
|
|||
|
if (current_function_needs_context)
|
|||
|
{
|
|||
|
emit_insn (gen_addpsi3 (stack_pointer_rtx, stack_pointer_rtx,
|
|||
|
GEN_INT (-4)));
|
|||
|
emit_move_insn (gen_rtx (MEM, PSImode, stack_pointer_rtx),
|
|||
|
gen_rtx (REG, PSImode, STATIC_CHAIN_REGNUM));
|
|||
|
}
|
|||
|
|
|||
|
if (frame_pointer_needed)
|
|||
|
{
|
|||
|
/* Store a2 into a0 temporarily. */
|
|||
|
emit_move_insn (gen_rtx (REG, PSImode, 4), frame_pointer_rtx);
|
|||
|
|
|||
|
/* Set up the frame pointer. */
|
|||
|
emit_move_insn (frame_pointer_rtx, stack_pointer_rtx);
|
|||
|
}
|
|||
|
|
|||
|
/* Make any necessary space for the saved registers and local frame. */
|
|||
|
if (size)
|
|||
|
emit_insn (gen_addpsi3 (stack_pointer_rtx, stack_pointer_rtx,
|
|||
|
GEN_INT (-size)));
|
|||
|
|
|||
|
/* Save the callee saved registers. They're saved into the top
|
|||
|
of the frame, using the stack pointer. */
|
|||
|
for (i = 0, offset = outgoing_args_size;
|
|||
|
i < FIRST_PSEUDO_REGISTER; i++)
|
|||
|
{
|
|||
|
if (regs_ever_live[i] && !call_used_regs[i] && ! fixed_regs[i]
|
|||
|
|| (i == FRAME_POINTER_REGNUM && frame_pointer_needed))
|
|||
|
{
|
|||
|
int regno;
|
|||
|
|
|||
|
/* If we're saving the frame pointer, then it will be found in
|
|||
|
register 4 (a0). */
|
|||
|
regno = (i == FRAME_POINTER_REGNUM && frame_pointer_needed) ? 4 : i;
|
|||
|
|
|||
|
emit_move_insn (gen_rtx (MEM, PSImode,
|
|||
|
gen_rtx (PLUS, Pmode,
|
|||
|
stack_pointer_rtx,
|
|||
|
GEN_INT (offset))),
|
|||
|
gen_rtx (REG, PSImode, regno));
|
|||
|
offset += 4;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* Now put the static chain back where the rest of the function
|
|||
|
expects to find it. */
|
|||
|
if (current_function_needs_context)
|
|||
|
{
|
|||
|
emit_move_insn (gen_rtx (REG, PSImode, STATIC_CHAIN_REGNUM),
|
|||
|
gen_rtx (MEM, PSImode,
|
|||
|
gen_rtx (PLUS, PSImode, stack_pointer_rtx,
|
|||
|
GEN_INT (size))));
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* Expand the epilogue into RTL. */
|
|||
|
void
|
|||
|
expand_epilogue ()
|
|||
|
{
|
|||
|
unsigned int size;
|
|||
|
unsigned int outgoing_args_size = current_function_outgoing_args_size;
|
|||
|
int offset, i, temp_regno;
|
|||
|
rtx basereg;
|
|||
|
|
|||
|
size = total_frame_size ();
|
|||
|
|
|||
|
if (DECL_RESULT (current_function_decl)
|
|||
|
&& DECL_RTL (DECL_RESULT (current_function_decl))
|
|||
|
&& REG_P (DECL_RTL (DECL_RESULT (current_function_decl))))
|
|||
|
temp_regno = (REGNO (DECL_RTL (DECL_RESULT (current_function_decl))) == 4
|
|||
|
? 0 : 4);
|
|||
|
else
|
|||
|
temp_regno = 4;
|
|||
|
|
|||
|
/* Emit an out of line epilogue sequence if it's profitable to do so. */
|
|||
|
if (out_of_line_epilogue)
|
|||
|
{
|
|||
|
/* If there were no outgoing arguments and no local frame, then
|
|||
|
we will be able to omit the rts at the end of this function,
|
|||
|
so just jump to the epilogue_noreturn routine. */
|
|||
|
if (get_frame_size () == 0 && outgoing_args_size == 0)
|
|||
|
{
|
|||
|
emit_jump_insn (gen_outline_epilogue_jump ());
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (outgoing_args_size)
|
|||
|
emit_insn (gen_addpsi3 (stack_pointer_rtx, stack_pointer_rtx,
|
|||
|
GEN_INT (outgoing_args_size)));
|
|||
|
|
|||
|
if (temp_regno == 0)
|
|||
|
emit_insn (gen_outline_epilogue_call_d0 ());
|
|||
|
else if (temp_regno == 4)
|
|||
|
emit_insn (gen_outline_epilogue_call_a0 ());
|
|||
|
|
|||
|
if (get_frame_size () != 0)
|
|||
|
emit_insn (gen_addpsi3 (stack_pointer_rtx, stack_pointer_rtx,
|
|||
|
GEN_INT (size - outgoing_args_size - 16)));
|
|||
|
emit_jump_insn (gen_return_internal ());
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
/* Registers are restored from the frame pointer if we have one,
|
|||
|
else they're restored from the stack pointer. Figure out
|
|||
|
the appropriate offset to the register save area for both cases. */
|
|||
|
if (frame_pointer_needed)
|
|||
|
{
|
|||
|
basereg = frame_pointer_rtx;
|
|||
|
offset = -(size - outgoing_args_size);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
basereg = stack_pointer_rtx;
|
|||
|
offset = outgoing_args_size;
|
|||
|
}
|
|||
|
|
|||
|
/* Restore each register. */
|
|||
|
for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
|
|||
|
{
|
|||
|
if (regs_ever_live[i] && !call_used_regs[i] && ! fixed_regs[i]
|
|||
|
|| (i == FRAME_POINTER_REGNUM && frame_pointer_needed))
|
|||
|
{
|
|||
|
int regno;
|
|||
|
|
|||
|
/* Restore the frame pointer (if it exists) into a temporary
|
|||
|
register. */
|
|||
|
regno = ((i == FRAME_POINTER_REGNUM && frame_pointer_needed)
|
|||
|
? temp_regno : i);
|
|||
|
|
|||
|
emit_move_insn (gen_rtx (REG, PSImode, regno),
|
|||
|
gen_rtx (MEM, PSImode,
|
|||
|
gen_rtx (PLUS, Pmode,
|
|||
|
basereg,
|
|||
|
GEN_INT (offset))));
|
|||
|
offset += 4;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (frame_pointer_needed)
|
|||
|
{
|
|||
|
/* Deallocate this frame's stack. */
|
|||
|
emit_move_insn (stack_pointer_rtx, frame_pointer_rtx);
|
|||
|
/* Restore the old frame pointer. */
|
|||
|
emit_move_insn (frame_pointer_rtx, gen_rtx (REG, PSImode, temp_regno));
|
|||
|
}
|
|||
|
else if (size)
|
|||
|
{
|
|||
|
/* Deallocate this function's stack. */
|
|||
|
emit_insn (gen_addpsi3 (stack_pointer_rtx, stack_pointer_rtx,
|
|||
|
GEN_INT (size)));
|
|||
|
}
|
|||
|
|
|||
|
/* If we had to allocate a slot to save the context pointer,
|
|||
|
then it must be deallocated here. */
|
|||
|
if (current_function_needs_context)
|
|||
|
emit_insn (gen_addpsi3 (stack_pointer_rtx, stack_pointer_rtx, GEN_INT (4)));
|
|||
|
|
|||
|
/* Emit the return insn, if this function had no stack, then we
|
|||
|
can use the standard return (which allows more optimizations),
|
|||
|
else we have to use the special one which inhibits optimizations. */
|
|||
|
if (size == 0 && !current_function_needs_context)
|
|||
|
emit_jump_insn (gen_return ());
|
|||
|
else
|
|||
|
emit_jump_insn (gen_return_internal ());
|
|||
|
}
|
|||
|
|
|||
|
/* 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 is in an unusable state. */
|
|||
|
CC_STATUS_INIT;
|
|||
|
cc_status.flags |= CC_OVERFLOW_UNUSABLE | CC_NO_CARRY;
|
|||
|
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 in an unusable state. */
|
|||
|
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_CLOBBER:
|
|||
|
/* Insn doesn't leave CC in a usable state. */
|
|||
|
CC_STATUS_INIT;
|
|||
|
break;
|
|||
|
|
|||
|
default:
|
|||
|
CC_STATUS_INIT;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* Return true if OP is a valid call operand. Valid call operands
|
|||
|
are SYMBOL_REFs and REGs. */
|
|||
|
int
|
|||
|
call_address_operand (op, mode)
|
|||
|
rtx op;
|
|||
|
enum machine_mode mode;
|
|||
|
{
|
|||
|
return (GET_CODE (op) == SYMBOL_REF || GET_CODE (op) == REG);
|
|||
|
}
|
|||
|
|
|||
|
/* Return true if OP is a memory operand with a constant address.
|
|||
|
A special PSImode move pattern uses this predicate. */
|
|||
|
int
|
|||
|
constant_memory_operand (op, mode)
|
|||
|
rtx op;
|
|||
|
enum machine_mode mode;
|
|||
|
{
|
|||
|
return GET_CODE (op) == MEM && CONSTANT_ADDRESS_P (XEXP (op, 0));
|
|||
|
}
|
|||
|
|
|||
|
/* 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, input)
|
|||
|
enum reg_class class;
|
|||
|
enum machine_mode mode;
|
|||
|
rtx in;
|
|||
|
int input;
|
|||
|
{
|
|||
|
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 (input
|
|||
|
&& GET_CODE (in) == MEM
|
|||
|
&& (mode == QImode)
|
|||
|
&& class == ADDRESS_REGS)
|
|||
|
return DATA_REGS;
|
|||
|
|
|||
|
/* Address register stores which are not PSImode need a scrach register. */
|
|||
|
if (! input
|
|||
|
&& GET_CODE (in) == MEM
|
|||
|
&& (mode != PSImode)
|
|||
|
&& class == ADDRESS_REGS)
|
|||
|
return DATA_REGS;
|
|||
|
|
|||
|
/* Otherwise assume no secondary reloads are needed. */
|
|||
|
return NO_REGS;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Shifts.
|
|||
|
|
|||
|
We devote a fair bit of code to getting efficient shifts since we can only
|
|||
|
shift one bit at a time, and each single bit shift may take multiple
|
|||
|
instructions.
|
|||
|
|
|||
|
The basic shift methods:
|
|||
|
|
|||
|
* loop shifts -- emit a loop using one (or two on H8/S) bit shifts;
|
|||
|
this is the default. SHIFT_LOOP
|
|||
|
|
|||
|
* inlined shifts -- emit straight line code for the shift; this is
|
|||
|
used when a straight line shift is about the same size or smaller
|
|||
|
than a loop. We allow the inline version to be slightly longer in
|
|||
|
some cases as it saves a register. SHIFT_INLINE
|
|||
|
|
|||
|
* There other oddballs. Not worth explaining. SHIFT_SPECIAL
|
|||
|
|
|||
|
|
|||
|
HImode shifts:
|
|||
|
|
|||
|
1-4 do them inline
|
|||
|
|
|||
|
5-7 If ashift, then multiply, else loop.
|
|||
|
|
|||
|
8-14 - If ashift, then multiply, if lshiftrt, then divide, else loop.
|
|||
|
15 - rotate the bit we want into the carry, clear the destination,
|
|||
|
(use mov 0,dst, not sub as sub will clobber the carry), then
|
|||
|
move bit into place.
|
|||
|
|
|||
|
Don't Panic, it's not nearly as bad as the H8 shifting code!!! */
|
|||
|
|
|||
|
int
|
|||
|
nshift_operator (x, mode)
|
|||
|
rtx x;
|
|||
|
enum machine_mode mode;
|
|||
|
{
|
|||
|
switch (GET_CODE (x))
|
|||
|
{
|
|||
|
case ASHIFTRT:
|
|||
|
case LSHIFTRT:
|
|||
|
case ASHIFT:
|
|||
|
return 1;
|
|||
|
|
|||
|
default:
|
|||
|
return 0;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* Called from the .md file to emit code to do shifts.
|
|||
|
Returns a boolean indicating success
|
|||
|
(currently this is always TRUE). */
|
|||
|
|
|||
|
int
|
|||
|
expand_a_shift (mode, code, operands)
|
|||
|
enum machine_mode mode;
|
|||
|
int code;
|
|||
|
rtx operands[];
|
|||
|
{
|
|||
|
emit_move_insn (operands[0], operands[1]);
|
|||
|
|
|||
|
/* need a loop to get all the bits we want - we generate the
|
|||
|
code at emit time, but need to allocate a scratch reg now */
|
|||
|
|
|||
|
emit_insn (gen_rtx
|
|||
|
(PARALLEL, VOIDmode,
|
|||
|
gen_rtvec (2,
|
|||
|
gen_rtx (SET, VOIDmode, operands[0],
|
|||
|
gen_rtx (code, mode,
|
|||
|
operands[0], operands[2])),
|
|||
|
gen_rtx (CLOBBER, VOIDmode,
|
|||
|
gen_rtx (SCRATCH, HImode, 0)))));
|
|||
|
|
|||
|
return 1;
|
|||
|
}
|
|||
|
|
|||
|
/* Shift algorithm determination.
|
|||
|
|
|||
|
There are various ways of doing a shift:
|
|||
|
SHIFT_INLINE: If the amount is small enough, just generate as many one-bit
|
|||
|
shifts as we need.
|
|||
|
SHIFT_SPECIAL: Hand crafted assembler.
|
|||
|
SHIFT_LOOP: If the above methods fail, just loop. */
|
|||
|
|
|||
|
enum shift_alg
|
|||
|
{
|
|||
|
SHIFT_INLINE,
|
|||
|
SHIFT_SPECIAL,
|
|||
|
SHIFT_LOOP,
|
|||
|
SHIFT_MAX
|
|||
|
};
|
|||
|
|
|||
|
/* Symbols of the various shifts which can be used as indices. */
|
|||
|
|
|||
|
enum shift_type
|
|||
|
{
|
|||
|
SHIFT_ASHIFT, SHIFT_LSHIFTRT, SHIFT_ASHIFTRT
|
|||
|
};
|
|||
|
|
|||
|
/* Symbols of the various modes which can be used as indices. */
|
|||
|
|
|||
|
enum shift_mode
|
|||
|
{
|
|||
|
HIshift,
|
|||
|
};
|
|||
|
|
|||
|
/* For single bit shift insns, record assembler and what bits of the
|
|||
|
condition code are valid afterwards (represented as various CC_FOO
|
|||
|
bits, 0 means CC isn't left in a usable state). */
|
|||
|
|
|||
|
struct shift_insn
|
|||
|
{
|
|||
|
char *assembler;
|
|||
|
int cc_valid;
|
|||
|
};
|
|||
|
|
|||
|
/* Assembler instruction shift table.
|
|||
|
|
|||
|
These tables are used to look up the basic shifts.
|
|||
|
They are indexed by cpu, shift_type, and mode.
|
|||
|
*/
|
|||
|
|
|||
|
static const struct shift_insn shift_one[3][3] =
|
|||
|
{
|
|||
|
{
|
|||
|
/* SHIFT_ASHIFT */
|
|||
|
{ "add\t%0,%0", CC_OVERFLOW_UNUSABLE | CC_NO_CARRY },
|
|||
|
},
|
|||
|
/* SHIFT_LSHIFTRT */
|
|||
|
{
|
|||
|
{ "lsr\t%0", CC_NO_CARRY },
|
|||
|
},
|
|||
|
/* SHIFT_ASHIFTRT */
|
|||
|
{
|
|||
|
{ "asr\t%0", CC_NO_CARRY },
|
|||
|
},
|
|||
|
};
|
|||
|
|
|||
|
/* Given CPU, MODE, SHIFT_TYPE, and shift count COUNT, determine the best
|
|||
|
algorithm for doing the shift. The assembler code is stored in ASSEMBLER.
|
|||
|
We don't achieve maximum efficiency in all cases, but the hooks are here
|
|||
|
to do so.
|
|||
|
|
|||
|
For now we just use lots of switch statements. Since we don't even come
|
|||
|
close to supporting all the cases, this is simplest. If this function ever
|
|||
|
gets too big, perhaps resort to a more table based lookup. Of course,
|
|||
|
at this point you may just wish to do it all in rtl. */
|
|||
|
|
|||
|
static enum shift_alg
|
|||
|
get_shift_alg (shift_type, mode, count, assembler_p, cc_valid_p)
|
|||
|
enum shift_type shift_type;
|
|||
|
enum machine_mode mode;
|
|||
|
int count;
|
|||
|
const char **assembler_p;
|
|||
|
int *cc_valid_p;
|
|||
|
{
|
|||
|
/* The default is to loop. */
|
|||
|
enum shift_alg alg = SHIFT_LOOP;
|
|||
|
enum shift_mode shift_mode;
|
|||
|
|
|||
|
/* We don't handle negative shifts or shifts greater than the word size,
|
|||
|
they should have been handled already. */
|
|||
|
|
|||
|
if (count < 0 || count > GET_MODE_BITSIZE (mode))
|
|||
|
abort ();
|
|||
|
|
|||
|
switch (mode)
|
|||
|
{
|
|||
|
case HImode:
|
|||
|
shift_mode = HIshift;
|
|||
|
break;
|
|||
|
default:
|
|||
|
abort ();
|
|||
|
}
|
|||
|
|
|||
|
/* Assume either SHIFT_LOOP or SHIFT_INLINE.
|
|||
|
It is up to the caller to know that looping clobbers cc. */
|
|||
|
*assembler_p = shift_one[shift_type][shift_mode].assembler;
|
|||
|
*cc_valid_p = shift_one[shift_type][shift_mode].cc_valid;
|
|||
|
|
|||
|
/* Now look for cases we want to optimize. */
|
|||
|
|
|||
|
switch (shift_mode)
|
|||
|
{
|
|||
|
case HIshift:
|
|||
|
if (count <= 4)
|
|||
|
return SHIFT_INLINE;
|
|||
|
else if (count < 15 && shift_type != SHIFT_ASHIFTRT)
|
|||
|
{
|
|||
|
switch (count)
|
|||
|
{
|
|||
|
case 5:
|
|||
|
if (shift_type == SHIFT_ASHIFT)
|
|||
|
*assembler_p = "mov 32,%4\n\tmul %4,%0";
|
|||
|
else if (shift_type == SHIFT_LSHIFTRT)
|
|||
|
*assembler_p
|
|||
|
= "sub %4,%4\n\tmov %4,mdr\n\tmov 32,%4\n\tdivu %4,%0";
|
|||
|
*cc_valid_p = CC_NO_CARRY;
|
|||
|
return SHIFT_SPECIAL;
|
|||
|
case 6:
|
|||
|
if (shift_type == SHIFT_ASHIFT)
|
|||
|
*assembler_p = "mov 64,%4\n\tmul %4,%0";
|
|||
|
else if (shift_type == SHIFT_LSHIFTRT)
|
|||
|
*assembler_p
|
|||
|
= "sub %4,%4\n\tmov %4,mdr\n\tmov 64,%4\n\tdivu %4,%0";
|
|||
|
*cc_valid_p = CC_NO_CARRY;
|
|||
|
return SHIFT_SPECIAL;
|
|||
|
case 7:
|
|||
|
if (shift_type == SHIFT_ASHIFT)
|
|||
|
*assembler_p = "mov 128,%4\n\tmul %4,%0";
|
|||
|
else if (shift_type == SHIFT_LSHIFTRT)
|
|||
|
*assembler_p
|
|||
|
= "sub %4,%4\n\tmov %4,mdr\n\tmov 128,%4\n\tdivu %4,%0";
|
|||
|
*cc_valid_p = CC_NO_CARRY;
|
|||
|
return SHIFT_SPECIAL;
|
|||
|
case 8:
|
|||
|
if (shift_type == SHIFT_ASHIFT)
|
|||
|
*assembler_p = "mov 256,%4\n\tmul %4,%0";
|
|||
|
else if (shift_type == SHIFT_LSHIFTRT)
|
|||
|
*assembler_p
|
|||
|
= "sub %4,%4\n\tmov %4,mdr\n\tmov 256,%4\n\tdivu %4,%0";
|
|||
|
*cc_valid_p = CC_NO_CARRY;
|
|||
|
return SHIFT_SPECIAL;
|
|||
|
case 9:
|
|||
|
if (shift_type == SHIFT_ASHIFT)
|
|||
|
*assembler_p = "mov 512,%4\n\tmul %4,%0";
|
|||
|
else if (shift_type == SHIFT_LSHIFTRT)
|
|||
|
*assembler_p
|
|||
|
= "sub %4,%4\n\tmov %4,mdr\n\tmov 512,%4\n\tdivu %4,%0";
|
|||
|
*cc_valid_p = CC_NO_CARRY;
|
|||
|
return SHIFT_SPECIAL;
|
|||
|
case 10:
|
|||
|
if (shift_type == SHIFT_ASHIFT)
|
|||
|
*assembler_p = "mov 1024,%4\n\tmul %4,%0";
|
|||
|
else if (shift_type == SHIFT_LSHIFTRT)
|
|||
|
*assembler_p
|
|||
|
= "sub %4,%4\n\tmov %4,mdr\n\tmov 1024,%4\n\tdivu %4,%0";
|
|||
|
*cc_valid_p = CC_NO_CARRY;
|
|||
|
return SHIFT_SPECIAL;
|
|||
|
case 11:
|
|||
|
if (shift_type == SHIFT_ASHIFT)
|
|||
|
*assembler_p = "mov 2048,%4\n\tmul %4,%0";
|
|||
|
else if (shift_type == SHIFT_LSHIFTRT)
|
|||
|
*assembler_p
|
|||
|
= "sub %4,%4\n\tmov %4,mdr\n\tmov 2048,%4\n\tdivu %4,%0";
|
|||
|
*cc_valid_p = CC_NO_CARRY;
|
|||
|
return SHIFT_SPECIAL;
|
|||
|
case 12:
|
|||
|
if (shift_type == SHIFT_ASHIFT)
|
|||
|
*assembler_p = "mov 4096,%4\n\tmul %4,%0";
|
|||
|
else if (shift_type == SHIFT_LSHIFTRT)
|
|||
|
*assembler_p
|
|||
|
= "sub %4,%4\n\tmov %4,mdr\n\tmov 4096,%4\n\tdivu %4,%0";
|
|||
|
*cc_valid_p = CC_NO_CARRY;
|
|||
|
return SHIFT_SPECIAL;
|
|||
|
case 13:
|
|||
|
if (shift_type == SHIFT_ASHIFT)
|
|||
|
*assembler_p = "mov 8192,%4\n\tmul %4,%0";
|
|||
|
else if (shift_type == SHIFT_LSHIFTRT)
|
|||
|
*assembler_p
|
|||
|
= "sub %4,%4\n\tmov %4,mdr\n\tmov 8192,%4\n\tdivu %4,%0";
|
|||
|
*cc_valid_p = CC_NO_CARRY;
|
|||
|
return SHIFT_SPECIAL;
|
|||
|
case 14:
|
|||
|
if (shift_type == SHIFT_ASHIFT)
|
|||
|
*assembler_p = "mov 16384,%4\n\tmul %4,%0";
|
|||
|
else if (shift_type == SHIFT_LSHIFTRT)
|
|||
|
*assembler_p
|
|||
|
= "sub %4,%4\n\tmov %4,mdr\n\tmov 16384,%4\n\tdivu %4,%0";
|
|||
|
*cc_valid_p = CC_NO_CARRY;
|
|||
|
return SHIFT_SPECIAL;
|
|||
|
}
|
|||
|
}
|
|||
|
else if (count == 15)
|
|||
|
{
|
|||
|
if (shift_type == SHIFT_ASHIFTRT)
|
|||
|
{
|
|||
|
*assembler_p = "add\t%0,%0\n\tsubc\t%0,%0\n";
|
|||
|
*cc_valid_p = CC_NO_CARRY;
|
|||
|
return SHIFT_SPECIAL;
|
|||
|
}
|
|||
|
if (shift_type == SHIFT_LSHIFTRT)
|
|||
|
{
|
|||
|
*assembler_p = "add\t%0,%0\n\tmov 0,%0\n\trol %0\n";
|
|||
|
*cc_valid_p = CC_NO_CARRY;
|
|||
|
return SHIFT_SPECIAL;
|
|||
|
}
|
|||
|
if (shift_type == SHIFT_ASHIFT)
|
|||
|
{
|
|||
|
*assembler_p = "ror\t%0\n\tmov 0,%0\n\tror %0\n";
|
|||
|
*cc_valid_p = CC_NO_CARRY;
|
|||
|
return SHIFT_SPECIAL;
|
|||
|
}
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
default:
|
|||
|
abort ();
|
|||
|
}
|
|||
|
|
|||
|
return alg;
|
|||
|
}
|
|||
|
|
|||
|
/* Emit the assembler code for doing shifts. */
|
|||
|
|
|||
|
char *
|
|||
|
emit_a_shift (insn, operands)
|
|||
|
rtx insn;
|
|||
|
rtx *operands;
|
|||
|
{
|
|||
|
static int loopend_lab;
|
|||
|
char *assembler;
|
|||
|
int cc_valid;
|
|||
|
rtx inside = PATTERN (insn);
|
|||
|
rtx shift = operands[3];
|
|||
|
enum machine_mode mode = GET_MODE (shift);
|
|||
|
enum rtx_code code = GET_CODE (shift);
|
|||
|
enum shift_type shift_type;
|
|||
|
enum shift_mode shift_mode;
|
|||
|
|
|||
|
loopend_lab++;
|
|||
|
|
|||
|
switch (mode)
|
|||
|
{
|
|||
|
case HImode:
|
|||
|
shift_mode = HIshift;
|
|||
|
break;
|
|||
|
default:
|
|||
|
abort ();
|
|||
|
}
|
|||
|
|
|||
|
switch (code)
|
|||
|
{
|
|||
|
case ASHIFTRT:
|
|||
|
shift_type = SHIFT_ASHIFTRT;
|
|||
|
break;
|
|||
|
case LSHIFTRT:
|
|||
|
shift_type = SHIFT_LSHIFTRT;
|
|||
|
break;
|
|||
|
case ASHIFT:
|
|||
|
shift_type = SHIFT_ASHIFT;
|
|||
|
break;
|
|||
|
default:
|
|||
|
abort ();
|
|||
|
}
|
|||
|
|
|||
|
if (GET_CODE (operands[2]) != CONST_INT)
|
|||
|
{
|
|||
|
/* Indexing by reg, so have to loop and test at top */
|
|||
|
output_asm_insn ("mov %2,%4", operands);
|
|||
|
output_asm_insn ("cmp 0,%4", operands);
|
|||
|
fprintf (asm_out_file, "\tble .Lle%d\n", loopend_lab);
|
|||
|
|
|||
|
/* Get the assembler code to do one shift. */
|
|||
|
get_shift_alg (shift_type, mode, 1, &assembler, &cc_valid);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
int n = INTVAL (operands[2]);
|
|||
|
enum shift_alg alg;
|
|||
|
|
|||
|
/* If the count is negative, make it 0. */
|
|||
|
if (n < 0)
|
|||
|
n = 0;
|
|||
|
/* If the count is too big, truncate it.
|
|||
|
ANSI says shifts of GET_MODE_BITSIZE are undefined - we choose to
|
|||
|
do the intuitive thing. */
|
|||
|
else if (n > GET_MODE_BITSIZE (mode))
|
|||
|
n = GET_MODE_BITSIZE (mode);
|
|||
|
|
|||
|
alg = get_shift_alg (shift_type, mode, n, &assembler, &cc_valid);
|
|||
|
|
|||
|
|
|||
|
switch (alg)
|
|||
|
{
|
|||
|
case SHIFT_INLINE:
|
|||
|
/* Emit one bit shifts. */
|
|||
|
while (n > 0)
|
|||
|
{
|
|||
|
output_asm_insn (assembler, operands);
|
|||
|
n -= 1;
|
|||
|
}
|
|||
|
|
|||
|
/* Keep track of CC. */
|
|||
|
if (cc_valid)
|
|||
|
{
|
|||
|
cc_status.value1 = operands[0];
|
|||
|
cc_status.flags |= cc_valid;
|
|||
|
}
|
|||
|
return "";
|
|||
|
|
|||
|
case SHIFT_SPECIAL:
|
|||
|
output_asm_insn (assembler, operands);
|
|||
|
|
|||
|
/* Keep track of CC. */
|
|||
|
if (cc_valid)
|
|||
|
{
|
|||
|
cc_status.value1 = operands[0];
|
|||
|
cc_status.flags |= cc_valid;
|
|||
|
}
|
|||
|
return "";
|
|||
|
}
|
|||
|
|
|||
|
{
|
|||
|
fprintf (asm_out_file, "\tmov %d,%s\n", n,
|
|||
|
reg_names[REGNO (operands[4])]);
|
|||
|
fprintf (asm_out_file, ".Llt%d:\n", loopend_lab);
|
|||
|
output_asm_insn (assembler, operands);
|
|||
|
output_asm_insn ("add -1,%4", operands);
|
|||
|
fprintf (asm_out_file, "\tbne .Llt%d\n", loopend_lab);
|
|||
|
return "";
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
fprintf (asm_out_file, ".Llt%d:\n", loopend_lab);
|
|||
|
output_asm_insn (assembler, operands);
|
|||
|
output_asm_insn ("add -1,%4", operands);
|
|||
|
fprintf (asm_out_file, "\tbne .Llt%d\n", loopend_lab);
|
|||
|
fprintf (asm_out_file, ".Lle%d:\n", loopend_lab);
|
|||
|
|
|||
|
return "";
|
|||
|
}
|
|||
|
|
|||
|
/* 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;
|
|||
|
|
|||
|
/* Only pass named arguments in registers. */
|
|||
|
if (!named)
|
|||
|
return NULL_RTX;
|
|||
|
|
|||
|
/* Figure out the size of the object to be passed. We lie and claim
|
|||
|
PSImode values are only two bytes since they fit in a single
|
|||
|
register. */
|
|||
|
if (mode == BLKmode)
|
|||
|
size = int_size_in_bytes (type);
|
|||
|
else if (mode == PSImode)
|
|||
|
size = 2;
|
|||
|
else
|
|||
|
size = GET_MODE_SIZE (mode);
|
|||
|
|
|||
|
/* Figure out the alignment of the object to be passed. */
|
|||
|
align = size;
|
|||
|
|
|||
|
cum->nbytes = (cum->nbytes + 1) & ~1;
|
|||
|
|
|||
|
/* Don't pass this arg via a register if all the argument registers
|
|||
|
are used up. */
|
|||
|
if (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;
|
|||
|
|
|||
|
return 0;
|
|||
|
/* Only pass named arguments in registers. */
|
|||
|
if (!named)
|
|||
|
return 0;
|
|||
|
|
|||
|
/* Figure out the size of the object to be passed. */
|
|||
|
if (mode == BLKmode)
|
|||
|
size = int_size_in_bytes (type);
|
|||
|
else if (mode == PSImode)
|
|||
|
size = 2;
|
|||
|
else
|
|||
|
size = GET_MODE_SIZE (mode);
|
|||
|
|
|||
|
/* Figure out the alignment of the object to be passed. */
|
|||
|
align = size;
|
|||
|
|
|||
|
cum->nbytes = (cum->nbytes + 1) & ~1;
|
|||
|
|
|||
|
/* 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;
|
|||
|
}
|
|||
|
|
|||
|
char *
|
|||
|
output_tst (operand, insn)
|
|||
|
rtx operand, insn;
|
|||
|
{
|
|||
|
|
|||
|
rtx temp;
|
|||
|
int past_call = 0;
|
|||
|
|
|||
|
/* Only tst insns using address registers can be optimized. */
|
|||
|
if (REGNO_REG_CLASS (REGNO (operand)) != ADDRESS_REGS)
|
|||
|
return "cmp 0,%0";
|
|||
|
|
|||
|
/* If testing an address register against zero, we can do better if
|
|||
|
we know there's a register already holding the value zero. First
|
|||
|
see if a global register has been set to zero, else we do a search
|
|||
|
for a register holding zero, if both of those fail, then we use a
|
|||
|
compare against zero. */
|
|||
|
if (zero_dreg || zero_areg)
|
|||
|
{
|
|||
|
rtx xoperands[2];
|
|||
|
xoperands[0] = operand;
|
|||
|
xoperands[1] = zero_dreg ? zero_dreg : 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 (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 register to zero?
|
|||
|
|
|||
|
If it's a call clobbered register, have we past a call? */
|
|||
|
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)
|
|||
|
&& (!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";
|
|||
|
}
|
|||
|
|
|||
|
/* Return nonzero if OP is a valid operand for a {zero,sign}_extendpsisi
|
|||
|
instruction.
|
|||
|
|
|||
|
It accepts anything that is a general operand or the sum of the
|
|||
|
stack pointer and a general operand. */
|
|||
|
extendpsi_operand (op, mode)
|
|||
|
rtx op;
|
|||
|
enum machine_mode mode;
|
|||
|
{
|
|||
|
return (general_operand (op, mode)
|
|||
|
|| (GET_CODE (op) == PLUS
|
|||
|
&& XEXP (op, 0) == stack_pointer_rtx
|
|||
|
&& general_operand (XEXP (op, 1), VOIDmode)));
|
|||
|
}
|