2285 lines
55 KiB
C
2285 lines
55 KiB
C
/* Subroutines for insn-output.c for NEC V850 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 <ctype.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"
|
||
|
||
/* True if the current function has anonymous arguments. */
|
||
int current_function_anonymous_args;
|
||
|
||
/* Information about the various small memory areas. */
|
||
struct small_memory_info small_memory[ (int)SMALL_MEMORY_max ] =
|
||
{
|
||
/* name value max physical max */
|
||
{ "tda", (char *)0, 0, 256 },
|
||
{ "sda", (char *)0, 0, 65536 },
|
||
{ "zda", (char *)0, 0, 32768 },
|
||
};
|
||
|
||
/* True if we don't need to check any more if the current
|
||
function is an interrupt handler */
|
||
static int v850_interrupt_cache_p = FALSE;
|
||
|
||
/* Whether current function is an interrupt handler. */
|
||
static int v850_interrupt_p = FALSE;
|
||
|
||
|
||
/* Sometimes certain combinations of command options do not make
|
||
sense on a particular target machine. You can define a macro
|
||
`OVERRIDE_OPTIONS' to take account of this. This macro, if
|
||
defined, is executed once just after all the command options have
|
||
been parsed.
|
||
|
||
Don't use this macro to turn on various extra optimizations for
|
||
`-O'. That is what `OPTIMIZATION_OPTIONS' is for. */
|
||
|
||
void
|
||
override_options ()
|
||
{
|
||
int i;
|
||
extern int atoi ();
|
||
|
||
/* Parse -m{s,t,z}da=nnn switches */
|
||
for (i = 0; i < (int)SMALL_MEMORY_max; i++)
|
||
{
|
||
if (small_memory[i].value)
|
||
{
|
||
if (!isdigit (*small_memory[i].value))
|
||
error ("%s=%s is not numeric.",
|
||
small_memory[i].name,
|
||
small_memory[i].value);
|
||
else
|
||
{
|
||
small_memory[i].max = atoi (small_memory[i].value);
|
||
if (small_memory[i].max > small_memory[i].physical_max)
|
||
error ("%s=%s is too large.",
|
||
small_memory[i].name,
|
||
small_memory[i].value);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/* Output assembly code for the start of the file. */
|
||
|
||
void
|
||
asm_file_start (file)
|
||
FILE *file;
|
||
{
|
||
output_file_directive (file, main_input_filename);
|
||
}
|
||
|
||
|
||
/* 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;
|
||
|
||
if (TARGET_GHS && !named)
|
||
return NULL_RTX;
|
||
|
||
if (mode == BLKmode)
|
||
size = int_size_in_bytes (type);
|
||
else
|
||
size = GET_MODE_SIZE (mode);
|
||
|
||
if (type)
|
||
align = TYPE_ALIGN (type) / BITS_PER_UNIT;
|
||
else
|
||
align = size;
|
||
|
||
cum->nbytes = (cum->nbytes + align - 1) &~(align - 1);
|
||
|
||
if (cum->nbytes > 4 * UNITS_PER_WORD)
|
||
return 0;
|
||
|
||
if (type == NULL_TREE
|
||
&& cum->nbytes + size > 4 * UNITS_PER_WORD)
|
||
return 0;
|
||
|
||
switch (cum->nbytes / UNITS_PER_WORD)
|
||
{
|
||
case 0:
|
||
result = gen_rtx (REG, mode, 6);
|
||
break;
|
||
case 1:
|
||
result = gen_rtx (REG, mode, 7);
|
||
break;
|
||
case 2:
|
||
result = gen_rtx (REG, mode, 8);
|
||
break;
|
||
case 3:
|
||
result = gen_rtx (REG, mode, 9);
|
||
break;
|
||
default:
|
||
result = 0;
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
|
||
/* Return the number of words which must be put into registers
|
||
for values which are part in registers and part 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;
|
||
|
||
if (TARGET_GHS && !named)
|
||
return 0;
|
||
|
||
if (mode == BLKmode)
|
||
size = int_size_in_bytes (type);
|
||
else
|
||
size = GET_MODE_SIZE (mode);
|
||
|
||
if (type)
|
||
align = TYPE_ALIGN (type) / BITS_PER_UNIT;
|
||
else
|
||
align = size;
|
||
|
||
cum->nbytes = (cum->nbytes + align - 1) &~(align - 1);
|
||
|
||
if (cum->nbytes > 4 * UNITS_PER_WORD)
|
||
return 0;
|
||
|
||
if (cum->nbytes + size <= 4 * UNITS_PER_WORD)
|
||
return 0;
|
||
|
||
if (type == NULL_TREE
|
||
&& cum->nbytes + size > 4 * UNITS_PER_WORD)
|
||
return 0;
|
||
|
||
return (4 * UNITS_PER_WORD - cum->nbytes) / UNITS_PER_WORD;
|
||
}
|
||
|
||
|
||
/* Return the high and low words of a CONST_DOUBLE */
|
||
|
||
static void
|
||
const_double_split (x, p_high, p_low)
|
||
rtx x;
|
||
HOST_WIDE_INT *p_high;
|
||
HOST_WIDE_INT *p_low;
|
||
{
|
||
if (GET_CODE (x) == CONST_DOUBLE)
|
||
{
|
||
long t[2];
|
||
REAL_VALUE_TYPE rv;
|
||
|
||
switch (GET_MODE (x))
|
||
{
|
||
case DFmode:
|
||
REAL_VALUE_FROM_CONST_DOUBLE (rv, x);
|
||
REAL_VALUE_TO_TARGET_DOUBLE (rv, t);
|
||
*p_high = t[1]; /* since v850 is little endian */
|
||
*p_low = t[0]; /* high is second word */
|
||
return;
|
||
|
||
case SFmode:
|
||
REAL_VALUE_FROM_CONST_DOUBLE (rv, x);
|
||
REAL_VALUE_TO_TARGET_SINGLE (rv, *p_high);
|
||
*p_low = 0;
|
||
return;
|
||
|
||
case VOIDmode:
|
||
case DImode:
|
||
*p_high = CONST_DOUBLE_HIGH (x);
|
||
*p_low = CONST_DOUBLE_LOW (x);
|
||
return;
|
||
}
|
||
}
|
||
|
||
fatal_insn ("const_double_split got a bad insn:", x);
|
||
}
|
||
|
||
|
||
/* Return the cost of the rtx R with code CODE. */
|
||
|
||
static int
|
||
const_costs_int (value, zero_cost)
|
||
HOST_WIDE_INT value;
|
||
int zero_cost;
|
||
{
|
||
if (CONST_OK_FOR_I (value))
|
||
return zero_cost;
|
||
else if (CONST_OK_FOR_J (value))
|
||
return 1;
|
||
else if (CONST_OK_FOR_K (value))
|
||
return 2;
|
||
else
|
||
return 4;
|
||
}
|
||
|
||
int
|
||
const_costs (r, c)
|
||
rtx r;
|
||
enum rtx_code c;
|
||
{
|
||
HOST_WIDE_INT high, low;
|
||
|
||
switch (c)
|
||
{
|
||
case CONST_INT:
|
||
return const_costs_int (INTVAL (r), 0);
|
||
|
||
case CONST_DOUBLE:
|
||
const_double_split (r, &high, &low);
|
||
if (GET_MODE (r) == SFmode)
|
||
return const_costs_int (high, 1);
|
||
else
|
||
return const_costs_int (high, 1) + const_costs_int (low, 1);
|
||
|
||
case SYMBOL_REF:
|
||
case LABEL_REF:
|
||
case CONST:
|
||
return 2;
|
||
|
||
case HIGH:
|
||
return 1;
|
||
|
||
default:
|
||
return 4;
|
||
}
|
||
}
|
||
|
||
|
||
/* 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;
|
||
{
|
||
HOST_WIDE_INT high, low;
|
||
|
||
switch (code)
|
||
{
|
||
case 'b':
|
||
case 'B':
|
||
case 'c':
|
||
case 'C':
|
||
switch ((code == 'B' || code == 'C')
|
||
? reverse_condition (GET_CODE (x)) : GET_CODE (x))
|
||
{
|
||
case NE:
|
||
if (code == 'c' || code == 'C')
|
||
fprintf (file, "nz");
|
||
else
|
||
fprintf (file, "ne");
|
||
break;
|
||
case EQ:
|
||
if (code == 'c' || code == 'C')
|
||
fprintf (file, "z");
|
||
else
|
||
fprintf (file, "e");
|
||
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, "nl");
|
||
break;
|
||
case GTU:
|
||
fprintf (file, "h");
|
||
break;
|
||
case LEU:
|
||
fprintf (file, "nh");
|
||
break;
|
||
case LTU:
|
||
fprintf (file, "l");
|
||
break;
|
||
default:
|
||
abort ();
|
||
}
|
||
break;
|
||
case 'F': /* high word of CONST_DOUBLE */
|
||
if (GET_CODE (x) == CONST_INT)
|
||
fprintf (file, "%d", (INTVAL (x) >= 0) ? 0 : -1);
|
||
else if (GET_CODE (x) == CONST_DOUBLE)
|
||
{
|
||
const_double_split (x, &high, &low);
|
||
fprintf (file, "%ld", (long) high);
|
||
}
|
||
else
|
||
abort ();
|
||
break;
|
||
case 'G': /* low word of CONST_DOUBLE */
|
||
if (GET_CODE (x) == CONST_INT)
|
||
fprintf (file, "%ld", (long) INTVAL (x));
|
||
else if (GET_CODE (x) == CONST_DOUBLE)
|
||
{
|
||
const_double_split (x, &high, &low);
|
||
fprintf (file, "%ld", (long) low);
|
||
}
|
||
else
|
||
abort ();
|
||
break;
|
||
case 'L':
|
||
fprintf (file, "%d\n", INTVAL (x) & 0xffff);
|
||
break;
|
||
case 'M':
|
||
fprintf (file, "%d", exact_log2 (INTVAL (x)));
|
||
break;
|
||
case 'O':
|
||
if (special_symbolref_operand (x, VOIDmode))
|
||
{
|
||
char* name;
|
||
|
||
if (GET_CODE (x) == SYMBOL_REF)
|
||
name = XSTR (x, 0);
|
||
else if (GET_CODE (x) == CONST)
|
||
name = XSTR (XEXP (XEXP (x, 0), 0), 0);
|
||
else
|
||
abort ();
|
||
|
||
if (ZDA_NAME_P (name))
|
||
fprintf (file, "zdaoff");
|
||
else if (SDA_NAME_P (name))
|
||
fprintf (file, "sdaoff");
|
||
else if (TDA_NAME_P (name))
|
||
fprintf (file, "tdaoff");
|
||
else
|
||
abort ();
|
||
}
|
||
else
|
||
abort ();
|
||
break;
|
||
case 'P':
|
||
if (special_symbolref_operand (x, VOIDmode))
|
||
output_addr_const (file, x);
|
||
else
|
||
abort ();
|
||
break;
|
||
case 'Q':
|
||
if (special_symbolref_operand (x, VOIDmode))
|
||
{
|
||
char* name;
|
||
|
||
if (GET_CODE (x) == SYMBOL_REF)
|
||
name = XSTR (x, 0);
|
||
else if (GET_CODE (x) == CONST)
|
||
name = XSTR (XEXP (XEXP (x, 0), 0), 0);
|
||
else
|
||
abort ();
|
||
|
||
if (ZDA_NAME_P (name))
|
||
fprintf (file, "r0");
|
||
else if (SDA_NAME_P (name))
|
||
fprintf (file, "gp");
|
||
else if (TDA_NAME_P (name))
|
||
fprintf (file, "ep");
|
||
else
|
||
abort ();
|
||
}
|
||
else
|
||
abort ();
|
||
break;
|
||
case 'R': /* 2nd word of a double. */
|
||
switch (GET_CODE (x))
|
||
{
|
||
case REG:
|
||
fprintf (file, reg_names[REGNO (x) + 1]);
|
||
break;
|
||
case MEM:
|
||
print_operand_address (file,
|
||
XEXP (adj_offsettable_operand (x, 4), 0));
|
||
break;
|
||
}
|
||
break;
|
||
case 'S':
|
||
{
|
||
/* if it's a reference to a TDA variable, use sst/sld vs. st/ld */
|
||
if (GET_CODE (x) == MEM && ep_memory_operand (x, GET_MODE (x), FALSE))
|
||
fputs ("s", file);
|
||
|
||
break;
|
||
}
|
||
case 'T':
|
||
{
|
||
/* Like an 'S' operand above, but for unsigned loads only. */
|
||
if (GET_CODE (x) == MEM && ep_memory_operand (x, GET_MODE (x), TRUE))
|
||
fputs ("s", file);
|
||
|
||
break;
|
||
}
|
||
case 'W': /* print the instruction suffix */
|
||
switch (GET_MODE (x))
|
||
{
|
||
default:
|
||
abort ();
|
||
|
||
case QImode: fputs (".b", file); break;
|
||
case HImode: fputs (".h", file); break;
|
||
case SImode: fputs (".w", file); break;
|
||
case SFmode: fputs (".w", file); break;
|
||
}
|
||
break;
|
||
case '.': /* register r0 */
|
||
fputs (reg_names[0], file);
|
||
break;
|
||
case 'z': /* reg or zero */
|
||
if (x == const0_rtx)
|
||
fputs (reg_names[0], file);
|
||
else if (GET_CODE (x) == REG)
|
||
fputs (reg_names[REGNO (x)], file);
|
||
else
|
||
abort ();
|
||
break;
|
||
default:
|
||
switch (GET_CODE (x))
|
||
{
|
||
case MEM:
|
||
if (GET_CODE (XEXP (x, 0)) == CONST_INT)
|
||
output_address (gen_rtx (PLUS, SImode,
|
||
gen_rtx (REG, SImode, 0),
|
||
XEXP (x, 0)));
|
||
else
|
||
output_address (XEXP (x, 0));
|
||
break;
|
||
|
||
case REG:
|
||
fputs (reg_names[REGNO (x)], file);
|
||
break;
|
||
case SUBREG:
|
||
fputs (reg_names[REGNO (SUBREG_REG (x)) + SUBREG_WORD (x)], file);
|
||
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:
|
||
fprintf (file, "0[");
|
||
print_operand (file, addr, 0);
|
||
fprintf (file, "]");
|
||
break;
|
||
case LO_SUM:
|
||
if (GET_CODE (XEXP (addr, 0)) == REG)
|
||
{
|
||
/* reg,foo */
|
||
fprintf (file, "lo(");
|
||
print_operand (file, XEXP (addr, 1), 0);
|
||
fprintf (file, ")[");
|
||
print_operand (file, XEXP (addr, 0), 0);
|
||
fprintf (file, "]");
|
||
}
|
||
break;
|
||
case PLUS:
|
||
if (GET_CODE (XEXP (addr, 0)) == REG
|
||
|| GET_CODE (XEXP (addr, 0)) == SUBREG)
|
||
{
|
||
/* reg,foo */
|
||
print_operand (file, XEXP (addr, 1), 0);
|
||
fprintf (file, "[");
|
||
print_operand (file, XEXP (addr, 0), 0);
|
||
fprintf (file, "]");
|
||
}
|
||
else
|
||
{
|
||
print_operand (file, XEXP (addr, 0), 0);
|
||
fprintf (file, "+");
|
||
print_operand (file, XEXP (addr, 1), 0);
|
||
}
|
||
break;
|
||
case SYMBOL_REF:
|
||
if (ENCODED_NAME_P (XSTR (addr, 0)))
|
||
{
|
||
char* name = XSTR (addr, 0);
|
||
char* off_name;
|
||
char* reg_name;
|
||
|
||
if (ZDA_NAME_P (name))
|
||
{
|
||
off_name = "zdaoff";
|
||
reg_name = "r0";
|
||
}
|
||
else if (SDA_NAME_P (name))
|
||
{
|
||
off_name = "sdaoff";
|
||
reg_name = "gp";
|
||
}
|
||
else if (TDA_NAME_P (name))
|
||
{
|
||
off_name = "tdaoff";
|
||
reg_name = "ep";
|
||
}
|
||
else
|
||
abort ();
|
||
|
||
fprintf (file, "%s(", off_name);
|
||
output_addr_const (file, addr);
|
||
fprintf (file, ")[%s]", reg_name);
|
||
}
|
||
else
|
||
output_addr_const (file, addr);
|
||
break;
|
||
case CONST:
|
||
if (special_symbolref_operand (addr, VOIDmode))
|
||
{
|
||
char* name = XSTR (XEXP (XEXP (addr, 0), 0), 0);
|
||
char* off_name;
|
||
char* reg_name;
|
||
|
||
if (ZDA_NAME_P (name))
|
||
{
|
||
off_name = "zdaoff";
|
||
reg_name = "r0";
|
||
}
|
||
else if (SDA_NAME_P (name))
|
||
{
|
||
off_name = "sdaoff";
|
||
reg_name = "gp";
|
||
}
|
||
else if (TDA_NAME_P (name))
|
||
{
|
||
off_name = "tdaoff";
|
||
reg_name = "ep";
|
||
}
|
||
else
|
||
abort ();
|
||
|
||
fprintf (file, "%s(", off_name);
|
||
output_addr_const (file, addr);
|
||
fprintf (file, ")[%s]", reg_name);
|
||
}
|
||
else
|
||
output_addr_const (file, addr);
|
||
break;
|
||
default:
|
||
output_addr_const (file, addr);
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
/* Return appropriate code to load up a 1, 2, or 4 integer/floating
|
||
point value. */
|
||
|
||
char *
|
||
output_move_single (operands)
|
||
rtx *operands;
|
||
{
|
||
rtx dst = operands[0];
|
||
rtx src = operands[1];
|
||
|
||
if (REG_P (dst))
|
||
{
|
||
if (REG_P (src))
|
||
return "mov %1,%0";
|
||
|
||
else if (GET_CODE (src) == CONST_INT)
|
||
{
|
||
HOST_WIDE_INT value = INTVAL (src);
|
||
|
||
if (CONST_OK_FOR_J (value)) /* signed 5 bit immediate */
|
||
return "mov %1,%0";
|
||
|
||
else if (CONST_OK_FOR_K (value)) /* signed 16 bit immediate */
|
||
return "movea lo(%1),%.,%0";
|
||
|
||
else if (CONST_OK_FOR_L (value)) /* upper 16 bits were set */
|
||
return "movhi hi(%1),%.,%0";
|
||
|
||
else /* random constant */
|
||
return "movhi hi(%1),%.,%0\n\tmovea lo(%1),%0,%0";
|
||
}
|
||
|
||
else if (GET_CODE (src) == CONST_DOUBLE && GET_MODE (src) == SFmode)
|
||
{
|
||
HOST_WIDE_INT high, low;
|
||
|
||
const_double_split (src, &high, &low);
|
||
if (CONST_OK_FOR_J (high)) /* signed 5 bit immediate */
|
||
return "mov %F1,%0";
|
||
|
||
else if (CONST_OK_FOR_K (high)) /* signed 16 bit immediate */
|
||
return "movea lo(%F1),%.,%0";
|
||
|
||
else if (CONST_OK_FOR_L (high)) /* upper 16 bits were set */
|
||
return "movhi hi(%F1),%.,%0";
|
||
|
||
else /* random constant */
|
||
return "movhi hi(%F1),%.,%0\n\tmovea lo(%F1),%0,%0";
|
||
}
|
||
|
||
else if (GET_CODE (src) == MEM)
|
||
return "%S1ld%W1 %1,%0";
|
||
|
||
else if (special_symbolref_operand (src, VOIDmode))
|
||
return "movea %O1(%P1),%Q1,%0";
|
||
|
||
else if (GET_CODE (src) == LABEL_REF
|
||
|| GET_CODE (src) == SYMBOL_REF
|
||
|| GET_CODE (src) == CONST)
|
||
{
|
||
return "movhi hi(%1),%.,%0\n\tmovea lo(%1),%0,%0";
|
||
}
|
||
|
||
else if (GET_CODE (src) == HIGH)
|
||
return "movhi hi(%1),%.,%0";
|
||
|
||
else if (GET_CODE (src) == LO_SUM)
|
||
{
|
||
operands[2] = XEXP (src, 0);
|
||
operands[3] = XEXP (src, 1);
|
||
return "movea lo(%3),%2,%0";
|
||
}
|
||
}
|
||
|
||
else if (GET_CODE (dst) == MEM)
|
||
{
|
||
if (REG_P (src))
|
||
return "%S0st%W0 %1,%0";
|
||
|
||
else if (GET_CODE (src) == CONST_INT && INTVAL (src) == 0)
|
||
return "%S0st%W0 %.,%0";
|
||
|
||
else if (GET_CODE (src) == CONST_DOUBLE
|
||
&& CONST0_RTX (GET_MODE (dst)) == src)
|
||
return "%S0st%W0 %.,%0";
|
||
}
|
||
|
||
fatal_insn ("output_move_single:", gen_rtx (SET, VOIDmode, dst, src));
|
||
return "";
|
||
}
|
||
|
||
|
||
/* Return appropriate code to load up an 8 byte integer or floating point value */
|
||
|
||
char *
|
||
output_move_double (operands)
|
||
rtx *operands;
|
||
{
|
||
enum machine_mode mode = GET_MODE (operands[0]);
|
||
rtx dst = operands[0];
|
||
rtx src = operands[1];
|
||
|
||
if (register_operand (dst, mode)
|
||
&& register_operand (src, mode))
|
||
{
|
||
if (REGNO (src) + 1 == REGNO (dst))
|
||
return "mov %R1,%R0\n\tmov %1,%0";
|
||
else
|
||
return "mov %1,%0\n\tmov %R1,%R0";
|
||
}
|
||
|
||
/* Storing 0 */
|
||
if (GET_CODE (dst) == MEM
|
||
&& ((GET_CODE (src) == CONST_INT && INTVAL (src) == 0)
|
||
|| (GET_CODE (src) == CONST_DOUBLE && CONST_DOUBLE_OK_FOR_G (src))))
|
||
return "st.w %.,%0\n\tst.w %.,%R0";
|
||
|
||
if (GET_CODE (src) == CONST_INT || GET_CODE (src) == CONST_DOUBLE)
|
||
{
|
||
HOST_WIDE_INT high_low[2];
|
||
int i;
|
||
rtx xop[10];
|
||
|
||
if (GET_CODE (src) == CONST_DOUBLE)
|
||
const_double_split (src, &high_low[1], &high_low[0]);
|
||
else
|
||
{
|
||
high_low[0] = INTVAL (src);
|
||
high_low[1] = (INTVAL (src) >= 0) ? 0 : -1;
|
||
}
|
||
|
||
for (i = 0; i < 2; i++)
|
||
{
|
||
xop[0] = gen_rtx (REG, SImode, REGNO (dst)+i);
|
||
xop[1] = GEN_INT (high_low[i]);
|
||
output_asm_insn (output_move_single (xop), xop);
|
||
}
|
||
|
||
return "";
|
||
}
|
||
|
||
if (GET_CODE (src) == MEM)
|
||
{
|
||
int ptrreg = -1;
|
||
int dreg = REGNO (dst);
|
||
rtx inside = XEXP (src, 0);
|
||
|
||
if (GET_CODE (inside) == REG)
|
||
ptrreg = REGNO (inside);
|
||
else if (GET_CODE (inside) == SUBREG)
|
||
ptrreg = REGNO (SUBREG_REG (inside)) + SUBREG_WORD (inside);
|
||
else if (GET_CODE (inside) == PLUS)
|
||
ptrreg = REGNO (XEXP (inside, 0));
|
||
else if (GET_CODE (inside) == LO_SUM)
|
||
ptrreg = REGNO (XEXP (inside, 0));
|
||
|
||
if (dreg == ptrreg)
|
||
return "ld.w %R1,%R0\n\tld.w %1,%0";
|
||
}
|
||
|
||
if (GET_CODE (src) == MEM)
|
||
return "ld.w %1,%0\n\tld.w %R1,%R0";
|
||
|
||
if (GET_CODE (dst) == MEM)
|
||
return "st.w %1,%0\n\tst.w %R1,%R0";
|
||
|
||
return "mov %1,%0\n\tmov %R1,%R0";
|
||
}
|
||
|
||
|
||
/* Return maximum offset supported for a short EP memory reference of mode
|
||
MODE and signedness UNSIGNEDP. */
|
||
|
||
int
|
||
ep_memory_offset (mode, unsignedp)
|
||
enum machine_mode mode;
|
||
int unsignedp;
|
||
{
|
||
int max_offset = 0;
|
||
|
||
switch (mode)
|
||
{
|
||
case QImode:
|
||
max_offset = (1 << 7);
|
||
break;
|
||
|
||
case HImode:
|
||
max_offset = (1 << 8);
|
||
break;
|
||
|
||
case SImode:
|
||
case SFmode:
|
||
max_offset = (1 << 8);
|
||
break;
|
||
}
|
||
|
||
return max_offset;
|
||
}
|
||
|
||
/* Return true if OP is a valid short EP memory reference */
|
||
|
||
int
|
||
ep_memory_operand (op, mode, unsigned_load)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
int unsigned_load;
|
||
{
|
||
rtx addr, op0, op1;
|
||
int max_offset;
|
||
int mask;
|
||
|
||
if (GET_CODE (op) != MEM)
|
||
return FALSE;
|
||
|
||
max_offset = ep_memory_offset (mode, unsigned_load);
|
||
|
||
mask = GET_MODE_SIZE (mode) - 1;
|
||
|
||
addr = XEXP (op, 0);
|
||
if (GET_CODE (addr) == CONST)
|
||
addr = XEXP (addr, 0);
|
||
|
||
switch (GET_CODE (addr))
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case SYMBOL_REF:
|
||
return TDA_NAME_P (XSTR (addr, 0));
|
||
|
||
case REG:
|
||
return REGNO (addr) == EP_REGNUM;
|
||
|
||
case PLUS:
|
||
op0 = XEXP (addr, 0);
|
||
op1 = XEXP (addr, 1);
|
||
if (GET_CODE (op1) == CONST_INT
|
||
&& INTVAL (op1) < max_offset
|
||
&& (INTVAL (op1) & mask) == 0)
|
||
{
|
||
if (GET_CODE (op0) == REG && REGNO (op0) == EP_REGNUM)
|
||
return TRUE;
|
||
|
||
if (GET_CODE (op0) == SYMBOL_REF && TDA_NAME_P (XSTR (op0, 0)))
|
||
return TRUE;
|
||
}
|
||
break;
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/* Return true if OP is either a register or 0 */
|
||
|
||
int
|
||
reg_or_0_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
if (GET_CODE (op) == CONST_INT)
|
||
return INTVAL (op) == 0;
|
||
|
||
else if (GET_CODE (op) == CONST_DOUBLE)
|
||
return CONST_DOUBLE_OK_FOR_G (op);
|
||
|
||
else
|
||
return register_operand (op, mode);
|
||
}
|
||
|
||
/* Return true if OP is either a register or a signed five bit integer */
|
||
|
||
int
|
||
reg_or_int5_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
if (GET_CODE (op) == CONST_INT)
|
||
return CONST_OK_FOR_J (INTVAL (op));
|
||
|
||
else
|
||
return register_operand (op, mode);
|
||
}
|
||
|
||
/* Return true if OP is a valid call operand. */
|
||
|
||
int
|
||
call_address_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
/* Only registers are valid call operands if TARGET_LONG_CALLS. */
|
||
if (TARGET_LONG_CALLS)
|
||
return GET_CODE (op) == REG;
|
||
return (GET_CODE (op) == SYMBOL_REF || GET_CODE (op) == REG);
|
||
}
|
||
|
||
int
|
||
special_symbolref_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
if (GET_CODE (op) == SYMBOL_REF)
|
||
return ENCODED_NAME_P (XSTR (op, 0));
|
||
|
||
else if (GET_CODE (op) == CONST)
|
||
return (GET_CODE (XEXP (op, 0)) == PLUS
|
||
&& GET_CODE (XEXP (XEXP (op, 0), 0)) == SYMBOL_REF
|
||
&& ENCODED_NAME_P (XSTR (XEXP (XEXP (op, 0), 0), 0))
|
||
&& GET_CODE (XEXP (XEXP (op, 0), 1)) == CONST_INT
|
||
&& CONST_OK_FOR_K (INTVAL (XEXP (XEXP (op, 0), 1))));
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
int
|
||
movsi_source_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
/* Some constants, as well as symbolic operands
|
||
must be done with HIGH & LO_SUM patterns. */
|
||
if (CONSTANT_P (op)
|
||
&& GET_CODE (op) != HIGH
|
||
&& !(GET_CODE (op) == CONST_INT
|
||
&& (CONST_OK_FOR_J (INTVAL (op))
|
||
|| CONST_OK_FOR_K (INTVAL (op))
|
||
|| CONST_OK_FOR_L (INTVAL (op)))))
|
||
return special_symbolref_operand (op, mode);
|
||
else
|
||
return general_operand (op, mode);
|
||
}
|
||
|
||
int
|
||
power_of_two_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
if (GET_CODE (op) != CONST_INT)
|
||
return 0;
|
||
|
||
if (exact_log2 (INTVAL (op)) == -1)
|
||
return 0;
|
||
return 1;
|
||
}
|
||
|
||
int
|
||
not_power_of_two_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
unsigned int mask;
|
||
|
||
if (mode == QImode)
|
||
mask = 0xff;
|
||
else if (mode == HImode)
|
||
mask = 0xffff;
|
||
else if (mode == SImode)
|
||
mask = 0xffffffff;
|
||
else
|
||
return 0;
|
||
|
||
if (GET_CODE (op) != CONST_INT)
|
||
return 0;
|
||
|
||
if (exact_log2 (~INTVAL (op) & mask) == -1)
|
||
return 0;
|
||
return 1;
|
||
}
|
||
|
||
|
||
/* Substitute memory references involving a pointer, to use the ep pointer,
|
||
taking care to save and preserve the ep. */
|
||
|
||
static void
|
||
substitute_ep_register (first_insn, last_insn, uses, regno, p_r1, p_ep)
|
||
rtx first_insn;
|
||
rtx last_insn;
|
||
int uses;
|
||
int regno;
|
||
rtx *p_r1;
|
||
rtx *p_ep;
|
||
{
|
||
rtx reg = gen_rtx (REG, Pmode, regno);
|
||
rtx insn;
|
||
int i;
|
||
|
||
if (!*p_r1)
|
||
{
|
||
regs_ever_live[1] = 1;
|
||
*p_r1 = gen_rtx (REG, Pmode, 1);
|
||
*p_ep = gen_rtx (REG, Pmode, 30);
|
||
}
|
||
|
||
if (TARGET_DEBUG)
|
||
fprintf (stderr, "Saved %d bytes (%d uses of register %s) in function %s, starting as insn %d, ending at %d\n",
|
||
2 * (uses - 3), uses, reg_names[regno],
|
||
IDENTIFIER_POINTER (DECL_NAME (current_function_decl)),
|
||
INSN_UID (first_insn), INSN_UID (last_insn));
|
||
|
||
if (GET_CODE (first_insn) == NOTE)
|
||
first_insn = next_nonnote_insn (first_insn);
|
||
|
||
last_insn = next_nonnote_insn (last_insn);
|
||
for (insn = first_insn; insn && insn != last_insn; insn = NEXT_INSN (insn))
|
||
{
|
||
if (GET_CODE (insn) == INSN)
|
||
{
|
||
rtx pattern = single_set (insn);
|
||
|
||
/* Replace the memory references. */
|
||
if (pattern)
|
||
{
|
||
rtx *p_mem;
|
||
/* Memory operands are signed by default. */
|
||
int unsignedp = FALSE;
|
||
|
||
if (GET_CODE (SET_DEST (pattern)) == MEM
|
||
&& GET_CODE (SET_SRC (pattern)) == MEM)
|
||
p_mem = (rtx *)0;
|
||
|
||
else if (GET_CODE (SET_DEST (pattern)) == MEM)
|
||
p_mem = &SET_DEST (pattern);
|
||
|
||
else if (GET_CODE (SET_SRC (pattern)) == MEM)
|
||
p_mem = &SET_SRC (pattern);
|
||
|
||
else
|
||
p_mem = (rtx *)0;
|
||
|
||
if (p_mem)
|
||
{
|
||
rtx addr = XEXP (*p_mem, 0);
|
||
|
||
if (GET_CODE (addr) == REG && REGNO (addr) == regno)
|
||
*p_mem = change_address (*p_mem, VOIDmode, *p_ep);
|
||
|
||
else if (GET_CODE (addr) == PLUS
|
||
&& GET_CODE (XEXP (addr, 0)) == REG
|
||
&& REGNO (XEXP (addr, 0)) == regno
|
||
&& GET_CODE (XEXP (addr, 1)) == CONST_INT
|
||
&& (((unsigned)INTVAL (XEXP (addr, 1)))
|
||
< ep_memory_offset (GET_MODE (*p_mem),
|
||
unsignedp)))
|
||
*p_mem = change_address (*p_mem, VOIDmode,
|
||
gen_rtx (PLUS, Pmode,
|
||
*p_ep, XEXP (addr, 1)));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Optimize back to back cases of ep <- r1 & r1 <- ep. */
|
||
insn = prev_nonnote_insn (first_insn);
|
||
if (insn && GET_CODE (insn) == INSN
|
||
&& GET_CODE (PATTERN (insn)) == SET
|
||
&& SET_DEST (PATTERN (insn)) == *p_ep
|
||
&& SET_SRC (PATTERN (insn)) == *p_r1)
|
||
delete_insn (insn);
|
||
else
|
||
emit_insn_before (gen_rtx (SET, Pmode, *p_r1, *p_ep), first_insn);
|
||
|
||
emit_insn_before (gen_rtx (SET, Pmode, *p_ep, reg), first_insn);
|
||
emit_insn_before (gen_rtx (SET, Pmode, *p_ep, *p_r1), last_insn);
|
||
}
|
||
|
||
|
||
/* In rare cases, correct code generation requires extra machine
|
||
dependent processing between the second jump optimization pass and
|
||
delayed branch scheduling. On those machines, define this macro
|
||
as a C statement to act on the code starting at INSN.
|
||
|
||
On the 850, we use it to implement the -mep mode to copy heavily used
|
||
pointers to ep to use the implicit addressing */
|
||
|
||
void v850_reorg (start_insn)
|
||
rtx start_insn;
|
||
{
|
||
struct {
|
||
int uses;
|
||
rtx first_insn;
|
||
rtx last_insn;
|
||
} regs[FIRST_PSEUDO_REGISTER];
|
||
|
||
int i;
|
||
int use_ep = FALSE;
|
||
rtx r1 = NULL_RTX;
|
||
rtx ep = NULL_RTX;
|
||
rtx insn;
|
||
rtx pattern;
|
||
|
||
/* If not ep mode, just return now */
|
||
if (!TARGET_EP)
|
||
return;
|
||
|
||
for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
|
||
{
|
||
regs[i].uses = 0;
|
||
regs[i].first_insn = NULL_RTX;
|
||
regs[i].last_insn = NULL_RTX;
|
||
}
|
||
|
||
for (insn = start_insn; insn != NULL_RTX; insn = NEXT_INSN (insn))
|
||
{
|
||
switch (GET_CODE (insn))
|
||
{
|
||
/* End of basic block */
|
||
default:
|
||
if (!use_ep)
|
||
{
|
||
int max_uses = -1;
|
||
int max_regno = -1;
|
||
|
||
for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
|
||
{
|
||
if (max_uses < regs[i].uses)
|
||
{
|
||
max_uses = regs[i].uses;
|
||
max_regno = i;
|
||
}
|
||
}
|
||
|
||
if (max_uses > 3)
|
||
substitute_ep_register (regs[max_regno].first_insn,
|
||
regs[max_regno].last_insn,
|
||
max_uses, max_regno, &r1, &ep);
|
||
}
|
||
|
||
use_ep = FALSE;
|
||
for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
|
||
{
|
||
regs[i].uses = 0;
|
||
regs[i].first_insn = NULL_RTX;
|
||
regs[i].last_insn = NULL_RTX;
|
||
}
|
||
break;
|
||
|
||
case NOTE:
|
||
break;
|
||
|
||
case INSN:
|
||
pattern = single_set (insn);
|
||
|
||
/* See if there are any memory references we can shorten */
|
||
if (pattern)
|
||
{
|
||
rtx src = SET_SRC (pattern);
|
||
rtx dest = SET_DEST (pattern);
|
||
rtx mem;
|
||
/* Memory operands are signed by default. */
|
||
int unsignedp = FALSE;
|
||
|
||
if (GET_CODE (dest) == MEM && GET_CODE (src) == MEM)
|
||
mem = NULL_RTX;
|
||
|
||
else if (GET_CODE (dest) == MEM)
|
||
mem = dest;
|
||
|
||
else if (GET_CODE (src) == MEM)
|
||
mem = src;
|
||
|
||
else
|
||
mem = NULL_RTX;
|
||
|
||
if (mem && ep_memory_operand (mem, GET_MODE (mem), unsignedp))
|
||
use_ep = TRUE;
|
||
|
||
else if (!use_ep && mem
|
||
&& GET_MODE_SIZE (GET_MODE (mem)) <= UNITS_PER_WORD)
|
||
{
|
||
rtx addr = XEXP (mem, 0);
|
||
int regno = -1;
|
||
int short_p;
|
||
|
||
if (GET_CODE (addr) == REG)
|
||
{
|
||
short_p = TRUE;
|
||
regno = REGNO (addr);
|
||
}
|
||
|
||
else if (GET_CODE (addr) == PLUS
|
||
&& GET_CODE (XEXP (addr, 0)) == REG
|
||
&& GET_CODE (XEXP (addr, 1)) == CONST_INT
|
||
&& (((unsigned)INTVAL (XEXP (addr, 1)))
|
||
< ep_memory_offset (GET_MODE (mem), unsignedp)))
|
||
{
|
||
short_p = TRUE;
|
||
regno = REGNO (XEXP (addr, 0));
|
||
}
|
||
|
||
else
|
||
short_p = FALSE;
|
||
|
||
if (short_p)
|
||
{
|
||
regs[regno].uses++;
|
||
regs[regno].last_insn = insn;
|
||
if (!regs[regno].first_insn)
|
||
regs[regno].first_insn = insn;
|
||
}
|
||
}
|
||
|
||
/* Loading up a register in the basic block zaps any savings
|
||
for the register */
|
||
if (GET_CODE (dest) == REG || GET_CODE (dest) == SUBREG)
|
||
{
|
||
enum machine_mode mode = GET_MODE (dest);
|
||
int word = 0;
|
||
int regno;
|
||
int endregno;
|
||
|
||
while (GET_CODE (dest) == SUBREG)
|
||
{
|
||
word = SUBREG_WORD (dest);
|
||
dest = SUBREG_REG (dest);
|
||
}
|
||
|
||
regno = REGNO (dest) + word;
|
||
endregno = regno + HARD_REGNO_NREGS (regno, mode);
|
||
|
||
if (!use_ep)
|
||
{
|
||
/* See if we can use the pointer before this
|
||
modification. */
|
||
int max_uses = -1;
|
||
int max_regno = -1;
|
||
|
||
for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
|
||
{
|
||
if (max_uses < regs[i].uses)
|
||
{
|
||
max_uses = regs[i].uses;
|
||
max_regno = i;
|
||
}
|
||
}
|
||
|
||
if (max_uses > 3
|
||
&& max_regno >= regno
|
||
&& max_regno < endregno)
|
||
{
|
||
substitute_ep_register (regs[max_regno].first_insn,
|
||
regs[max_regno].last_insn,
|
||
max_uses, max_regno, &r1, &ep);
|
||
|
||
/* Since we made a substitution, zap all remembered
|
||
registers. */
|
||
for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
|
||
{
|
||
regs[i].uses = 0;
|
||
regs[i].first_insn = NULL_RTX;
|
||
regs[i].last_insn = NULL_RTX;
|
||
}
|
||
}
|
||
}
|
||
|
||
for (i = regno; i < endregno; i++)
|
||
{
|
||
regs[i].uses = 0;
|
||
regs[i].first_insn = NULL_RTX;
|
||
regs[i].last_insn = NULL_RTX;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/* # of registers saved by the interrupt handler. */
|
||
#define INTERRUPT_FIXED_NUM 4
|
||
|
||
/* # of bytes for registers saved by the interrupt handler. */
|
||
#define INTERRUPT_FIXED_SAVE_SIZE (4 * INTERRUPT_FIXED_NUM)
|
||
|
||
/* # of registers saved in register parameter area. */
|
||
#define INTERRUPT_REGPARM_NUM 4
|
||
/* # of words saved for other registers. */
|
||
#define INTERRUPT_ALL_SAVE_NUM \
|
||
(30 - INTERRUPT_FIXED_NUM + INTERRUPT_REGPARM_NUM)
|
||
|
||
#define INTERRUPT_ALL_SAVE_SIZE (4 * INTERRUPT_ALL_SAVE_NUM)
|
||
|
||
int
|
||
compute_register_save_size (p_reg_saved)
|
||
long *p_reg_saved;
|
||
{
|
||
int size = 0;
|
||
int i;
|
||
int interrupt_handler = v850_interrupt_function_p (current_function_decl);
|
||
int call_p = regs_ever_live[31];
|
||
long reg_saved = 0;
|
||
|
||
/* Count the return pointer if we need to save it. */
|
||
if (profile_flag && !call_p)
|
||
regs_ever_live[31] = call_p = 1;
|
||
|
||
/* Count space for the register saves. */
|
||
if (interrupt_handler)
|
||
{
|
||
for (i = 0; i <= 31; i++)
|
||
switch (i)
|
||
{
|
||
default:
|
||
if (regs_ever_live[i] || call_p)
|
||
{
|
||
size += 4;
|
||
reg_saved |= 1L << i;
|
||
}
|
||
break;
|
||
|
||
/* We don't save/restore r0 or the stack pointer */
|
||
case 0:
|
||
case STACK_POINTER_REGNUM:
|
||
break;
|
||
|
||
/* For registers with fixed use, we save them, set them to the
|
||
appropriate value, and then restore them.
|
||
These registers are handled specially, so don't list them
|
||
on the list of registers to save in the prologue. */
|
||
case 1: /* temp used to hold ep */
|
||
case 4: /* gp */
|
||
case 10: /* temp used to call interrupt save/restore */
|
||
case EP_REGNUM: /* ep */
|
||
size += 4;
|
||
break;
|
||
}
|
||
}
|
||
|
||
else
|
||
for (i = 0; i <= 31; i++)
|
||
if (regs_ever_live[i] && ((! call_used_regs[i]) || i == 31))
|
||
{
|
||
size += 4;
|
||
reg_saved |= 1L << i;
|
||
}
|
||
|
||
if (p_reg_saved)
|
||
*p_reg_saved = reg_saved;
|
||
|
||
return size;
|
||
}
|
||
|
||
int
|
||
compute_frame_size (size, p_reg_saved)
|
||
int size;
|
||
long *p_reg_saved;
|
||
{
|
||
extern int current_function_outgoing_args_size;
|
||
|
||
return (size
|
||
+ compute_register_save_size (p_reg_saved)
|
||
+ current_function_outgoing_args_size);
|
||
}
|
||
|
||
|
||
void
|
||
expand_prologue ()
|
||
{
|
||
unsigned int i;
|
||
int offset;
|
||
unsigned int size = get_frame_size ();
|
||
unsigned int actual_fsize;
|
||
unsigned int init_stack_alloc = 0;
|
||
rtx save_regs[32];
|
||
rtx save_all;
|
||
int num_save;
|
||
int default_stack;
|
||
int code;
|
||
int interrupt_handler = v850_interrupt_function_p (current_function_decl);
|
||
long reg_saved = 0;
|
||
|
||
actual_fsize = compute_frame_size (size, ®_saved);
|
||
|
||
/* Save/setup global registers for interrupt functions right now */
|
||
if (interrupt_handler)
|
||
{
|
||
emit_insn (gen_save_interrupt ());
|
||
actual_fsize -= INTERRUPT_FIXED_SAVE_SIZE;
|
||
if (((1L << 31) & reg_saved) != 0)
|
||
actual_fsize -= INTERRUPT_ALL_SAVE_SIZE;
|
||
}
|
||
|
||
/* Save arg registers to the stack if necessary. */
|
||
else if (current_function_anonymous_args)
|
||
{
|
||
if (TARGET_PROLOG_FUNCTION)
|
||
emit_insn (gen_save_r6_r9 ());
|
||
else
|
||
{
|
||
offset = 0;
|
||
for (i = 6; i < 10; i++)
|
||
{
|
||
emit_move_insn (gen_rtx (MEM, SImode,
|
||
plus_constant (stack_pointer_rtx,
|
||
offset)),
|
||
gen_rtx (REG, SImode, i));
|
||
offset += 4;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Identify all of the saved registers */
|
||
num_save = 0;
|
||
default_stack = 0;
|
||
for (i = 1; i < 31; i++)
|
||
{
|
||
if (((1L << i) & reg_saved) != 0)
|
||
save_regs[num_save++] = gen_rtx (REG, Pmode, i);
|
||
}
|
||
|
||
/* If the return pointer is saved, the helper functions also allocate
|
||
16 bytes of stack for arguments to be saved in. */
|
||
if (((1L << 31) & reg_saved) != 0)
|
||
{
|
||
save_regs[num_save++] = gen_rtx (REG, Pmode, 31);
|
||
default_stack = 16;
|
||
}
|
||
|
||
/* See if we have an insn that allocates stack space and saves the particular
|
||
registers we want to. */
|
||
save_all = NULL_RTX;
|
||
if (TARGET_PROLOG_FUNCTION && num_save > 0 && actual_fsize >= default_stack)
|
||
{
|
||
int alloc_stack = (4 * num_save) + default_stack;
|
||
int unalloc_stack = actual_fsize - alloc_stack;
|
||
int save_func_len = 4;
|
||
int save_normal_len;
|
||
|
||
if (unalloc_stack)
|
||
save_func_len += CONST_OK_FOR_J (unalloc_stack) ? 2 : 4;
|
||
|
||
/* see if we would have used ep to save the stack */
|
||
if (TARGET_EP && num_save > 3 && (unsigned)actual_fsize < 255)
|
||
save_normal_len = (3 * 2) + (2 * num_save);
|
||
else
|
||
save_normal_len = 4 * num_save;
|
||
|
||
save_normal_len += CONST_OK_FOR_J (actual_fsize) ? 2 : 4;
|
||
|
||
/* Don't bother checking if we don't actually save any space.
|
||
This happens for instance if one register is saved and additional
|
||
stack space is allocated. */
|
||
if (save_func_len < save_normal_len)
|
||
{
|
||
save_all = gen_rtx (PARALLEL, VOIDmode, rtvec_alloc (num_save + (TARGET_V850 ? 2 : 1)));
|
||
XVECEXP (save_all, 0, 0) = gen_rtx (SET, VOIDmode,
|
||
stack_pointer_rtx,
|
||
gen_rtx (PLUS, Pmode,
|
||
stack_pointer_rtx,
|
||
GEN_INT (-alloc_stack)));
|
||
|
||
if (TARGET_V850)
|
||
{
|
||
XVECEXP (save_all, 0, num_save+1)
|
||
= gen_rtx (CLOBBER, VOIDmode, gen_rtx (REG, Pmode, 10));
|
||
}
|
||
|
||
offset = - default_stack;
|
||
for (i = 0; i < num_save; i++)
|
||
{
|
||
XVECEXP (save_all, 0, i+1)
|
||
= gen_rtx (SET, VOIDmode,
|
||
gen_rtx (MEM, Pmode,
|
||
plus_constant (stack_pointer_rtx, offset)),
|
||
save_regs[i]);
|
||
offset -= 4;
|
||
}
|
||
|
||
code = recog (save_all, NULL_RTX, NULL_PTR);
|
||
if (code >= 0)
|
||
{
|
||
rtx insn = emit_insn (save_all);
|
||
INSN_CODE (insn) = code;
|
||
actual_fsize -= alloc_stack;
|
||
|
||
if (TARGET_DEBUG)
|
||
fprintf (stderr, "Saved %d bytes via prologue function (%d vs. %d) for function %s\n",
|
||
save_normal_len - save_func_len,
|
||
save_normal_len, save_func_len,
|
||
IDENTIFIER_POINTER (DECL_NAME (current_function_decl)));
|
||
}
|
||
else
|
||
save_all = NULL_RTX;
|
||
}
|
||
}
|
||
|
||
/* If no prolog save function is available, store the registers the old fashioned
|
||
way (one by one). */
|
||
if (!save_all)
|
||
{
|
||
/* Special case interrupt functions that save all registers for a call. */
|
||
if (interrupt_handler && ((1L << 31) & reg_saved) != 0)
|
||
emit_insn (gen_save_all_interrupt ());
|
||
|
||
else
|
||
{
|
||
/* If the stack is too big, allocate it in chunks so we can do the
|
||
register saves. We use the register save size so we use the ep
|
||
register. */
|
||
if (actual_fsize && !CONST_OK_FOR_K (-actual_fsize))
|
||
init_stack_alloc = compute_register_save_size (NULL);
|
||
else
|
||
init_stack_alloc = actual_fsize;
|
||
|
||
/* Save registers at the beginning of the stack frame */
|
||
offset = init_stack_alloc - 4;
|
||
|
||
if (init_stack_alloc)
|
||
emit_insn (gen_addsi3 (stack_pointer_rtx,
|
||
stack_pointer_rtx,
|
||
GEN_INT (-init_stack_alloc)));
|
||
|
||
/* Save the return pointer first. */
|
||
if (num_save > 0 && REGNO (save_regs[num_save-1]) == 31)
|
||
{
|
||
emit_move_insn (gen_rtx (MEM, SImode,
|
||
plus_constant (stack_pointer_rtx,
|
||
offset)),
|
||
save_regs[--num_save]);
|
||
offset -= 4;
|
||
}
|
||
|
||
for (i = 0; i < num_save; i++)
|
||
{
|
||
emit_move_insn (gen_rtx (MEM, SImode,
|
||
plus_constant (stack_pointer_rtx,
|
||
offset)),
|
||
save_regs[i]);
|
||
offset -= 4;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Allocate the rest of the stack that was not allocated above (either it is
|
||
> 32K or we just called a function to save the registers and needed more
|
||
stack. */
|
||
if (actual_fsize > init_stack_alloc)
|
||
{
|
||
int diff = actual_fsize - init_stack_alloc;
|
||
if (CONST_OK_FOR_K (diff))
|
||
emit_insn (gen_addsi3 (stack_pointer_rtx,
|
||
stack_pointer_rtx,
|
||
GEN_INT (-diff)));
|
||
else
|
||
{
|
||
rtx reg = gen_rtx (REG, Pmode, 12);
|
||
emit_move_insn (reg, GEN_INT (-diff));
|
||
emit_insn (gen_addsi3 (stack_pointer_rtx, stack_pointer_rtx, reg));
|
||
}
|
||
}
|
||
|
||
/* If we need a frame pointer, set it up now. */
|
||
if (frame_pointer_needed)
|
||
emit_move_insn (hard_frame_pointer_rtx, stack_pointer_rtx);
|
||
}
|
||
|
||
|
||
void
|
||
expand_epilogue ()
|
||
{
|
||
unsigned int i;
|
||
int offset;
|
||
unsigned int size = get_frame_size ();
|
||
long reg_saved = 0;
|
||
unsigned int actual_fsize = compute_frame_size (size, ®_saved);
|
||
unsigned int init_stack_free = 0;
|
||
rtx restore_regs[32];
|
||
rtx restore_all;
|
||
int num_restore;
|
||
int default_stack;
|
||
int code;
|
||
int interrupt_handler = v850_interrupt_function_p (current_function_decl);
|
||
|
||
/* Eliminate the initial stack stored by interrupt functions. */
|
||
if (interrupt_handler)
|
||
{
|
||
actual_fsize -= INTERRUPT_FIXED_SAVE_SIZE;
|
||
if (((1L << 31) & reg_saved) != 0)
|
||
actual_fsize -= INTERRUPT_ALL_SAVE_SIZE;
|
||
}
|
||
|
||
/* Cut off any dynamic stack created. */
|
||
if (frame_pointer_needed)
|
||
emit_move_insn (stack_pointer_rtx, hard_frame_pointer_rtx);
|
||
|
||
/* Identify all of the saved registers */
|
||
num_restore = 0;
|
||
default_stack = 0;
|
||
for (i = 1; i < 31; i++)
|
||
{
|
||
if (((1L << i) & reg_saved) != 0)
|
||
restore_regs[num_restore++] = gen_rtx (REG, Pmode, i);
|
||
}
|
||
|
||
/* If the return pointer is saved, the helper functions also allocate
|
||
16 bytes of stack for arguments to be saved in. */
|
||
if (((1L << 31) & reg_saved) != 0)
|
||
{
|
||
restore_regs[num_restore++] = gen_rtx (REG, Pmode, 31);
|
||
default_stack = 16;
|
||
}
|
||
|
||
/* See if we have an insn that restores the particular registers we
|
||
want to. */
|
||
restore_all = NULL_RTX;
|
||
if (TARGET_PROLOG_FUNCTION && num_restore > 0 && actual_fsize >= default_stack
|
||
&& !interrupt_handler)
|
||
{
|
||
int alloc_stack = (4 * num_restore) + default_stack;
|
||
int unalloc_stack = actual_fsize - alloc_stack;
|
||
int restore_func_len = 4;
|
||
int restore_normal_len;
|
||
|
||
if (unalloc_stack)
|
||
restore_func_len += CONST_OK_FOR_J (unalloc_stack) ? 2 : 4;
|
||
|
||
/* see if we would have used ep to restore the registers */
|
||
if (TARGET_EP && num_restore > 3 && (unsigned)actual_fsize < 255)
|
||
restore_normal_len = (3 * 2) + (2 * num_restore);
|
||
else
|
||
restore_normal_len = 4 * num_restore;
|
||
|
||
restore_normal_len += (CONST_OK_FOR_J (actual_fsize) ? 2 : 4) + 2;
|
||
|
||
/* Don't bother checking if we don't actually save any space. */
|
||
if (restore_func_len < restore_normal_len)
|
||
{
|
||
restore_all = gen_rtx (PARALLEL, VOIDmode,
|
||
rtvec_alloc (num_restore + 2));
|
||
XVECEXP (restore_all, 0, 0) = gen_rtx (RETURN, VOIDmode);
|
||
XVECEXP (restore_all, 0, 1)
|
||
= gen_rtx (SET, VOIDmode, stack_pointer_rtx,
|
||
gen_rtx (PLUS, Pmode,
|
||
stack_pointer_rtx,
|
||
GEN_INT (alloc_stack)));
|
||
|
||
offset = alloc_stack - 4;
|
||
for (i = 0; i < num_restore; i++)
|
||
{
|
||
XVECEXP (restore_all, 0, i+2)
|
||
= gen_rtx (SET, VOIDmode,
|
||
restore_regs[i],
|
||
gen_rtx (MEM, Pmode,
|
||
plus_constant (stack_pointer_rtx, offset)));
|
||
offset -= 4;
|
||
}
|
||
|
||
code = recog (restore_all, NULL_RTX, NULL_PTR);
|
||
if (code >= 0)
|
||
{
|
||
rtx insn;
|
||
|
||
actual_fsize -= alloc_stack;
|
||
if (actual_fsize)
|
||
{
|
||
if (CONST_OK_FOR_K (actual_fsize))
|
||
emit_insn (gen_addsi3 (stack_pointer_rtx,
|
||
stack_pointer_rtx,
|
||
GEN_INT (actual_fsize)));
|
||
else
|
||
{
|
||
rtx reg = gen_rtx (REG, Pmode, 12);
|
||
emit_move_insn (reg, GEN_INT (actual_fsize));
|
||
emit_insn (gen_addsi3 (stack_pointer_rtx,
|
||
stack_pointer_rtx,
|
||
reg));
|
||
}
|
||
}
|
||
|
||
insn = emit_jump_insn (restore_all);
|
||
INSN_CODE (insn) = code;
|
||
|
||
if (TARGET_DEBUG)
|
||
fprintf (stderr, "Saved %d bytes via epilogue function (%d vs. %d) in function %s\n",
|
||
restore_normal_len - restore_func_len,
|
||
restore_normal_len, restore_func_len,
|
||
IDENTIFIER_POINTER (DECL_NAME (current_function_decl)));
|
||
}
|
||
else
|
||
restore_all = NULL_RTX;
|
||
}
|
||
}
|
||
|
||
/* If no epilog save function is available, restore the registers the
|
||
old fashioned way (one by one). */
|
||
if (!restore_all)
|
||
{
|
||
/* If the stack is large, we need to cut it down in 2 pieces. */
|
||
if (actual_fsize && !CONST_OK_FOR_K (-actual_fsize))
|
||
init_stack_free = 4 * num_restore;
|
||
else
|
||
init_stack_free = actual_fsize;
|
||
|
||
/* Deallocate the rest of the stack if it is > 32K or if extra stack
|
||
was allocated for an interrupt handler that makes a call. */
|
||
if (actual_fsize > init_stack_free || (interrupt_handler && actual_fsize))
|
||
{
|
||
int diff = actual_fsize - ((interrupt_handler) ? 0 : init_stack_free);
|
||
if (CONST_OK_FOR_K (diff))
|
||
emit_insn (gen_addsi3 (stack_pointer_rtx,
|
||
stack_pointer_rtx,
|
||
GEN_INT (diff)));
|
||
else
|
||
{
|
||
rtx reg = gen_rtx (REG, Pmode, 12);
|
||
emit_move_insn (reg, GEN_INT (diff));
|
||
emit_insn (gen_addsi3 (stack_pointer_rtx,
|
||
stack_pointer_rtx,
|
||
reg));
|
||
}
|
||
}
|
||
|
||
/* Special case interrupt functions that save all registers
|
||
for a call. */
|
||
if (interrupt_handler && ((1L << 31) & reg_saved) != 0)
|
||
emit_insn (gen_restore_all_interrupt ());
|
||
else
|
||
{
|
||
/* Restore registers from the beginning of the stack frame */
|
||
offset = init_stack_free - 4;
|
||
|
||
/* Restore the return pointer first. */
|
||
if (num_restore > 0 && REGNO (restore_regs[num_restore-1]) == 31)
|
||
{
|
||
emit_move_insn (restore_regs[--num_restore],
|
||
gen_rtx (MEM, SImode,
|
||
plus_constant (stack_pointer_rtx,
|
||
offset)));
|
||
offset -= 4;
|
||
}
|
||
|
||
for (i = 0; i < num_restore; i++)
|
||
{
|
||
emit_move_insn (restore_regs[i],
|
||
gen_rtx (MEM, SImode,
|
||
plus_constant (stack_pointer_rtx,
|
||
offset)));
|
||
|
||
offset -= 4;
|
||
}
|
||
|
||
/* Cut back the remainder of the stack. */
|
||
if (init_stack_free)
|
||
emit_insn (gen_addsi3 (stack_pointer_rtx,
|
||
stack_pointer_rtx,
|
||
GEN_INT (init_stack_free)));
|
||
}
|
||
|
||
/* And return or use reti for interrupt handlers. */
|
||
if (interrupt_handler)
|
||
emit_jump_insn (gen_restore_interrupt ());
|
||
else if (actual_fsize)
|
||
emit_jump_insn (gen_return_internal ());
|
||
else
|
||
emit_jump_insn (gen_return ());
|
||
}
|
||
|
||
current_function_anonymous_args = 0;
|
||
v850_interrupt_cache_p = FALSE;
|
||
v850_interrupt_p = FALSE;
|
||
}
|
||
|
||
|
||
/* 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;
|
||
}
|
||
}
|
||
|
||
|
||
/* Return nonzero if ATTR is a valid attribute for DECL.
|
||
ATTRIBUTES are any existing attributes and ARGS are the arguments
|
||
supplied with ATTR.
|
||
|
||
Supported attributes:
|
||
|
||
interrupt_handler or interrupt: output a prologue and epilogue suitable
|
||
for an interrupt handler. */
|
||
|
||
int
|
||
v850_valid_machine_decl_attribute (decl, attributes, attr, args)
|
||
tree decl;
|
||
tree attributes;
|
||
tree attr;
|
||
tree args;
|
||
{
|
||
if (args != NULL_TREE)
|
||
return 0;
|
||
|
||
if (is_attribute_p ("interrupt_handler", attr)
|
||
|| is_attribute_p ("interrupt", attr))
|
||
return TREE_CODE (decl) == FUNCTION_DECL;
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
/* Return nonzero if FUNC is an interrupt function as specified
|
||
by the "interrupt" attribute. */
|
||
|
||
int
|
||
v850_interrupt_function_p (func)
|
||
tree func;
|
||
{
|
||
tree a;
|
||
int ret = 0;
|
||
|
||
if (v850_interrupt_cache_p)
|
||
return v850_interrupt_p;
|
||
|
||
if (TREE_CODE (func) != FUNCTION_DECL)
|
||
return 0;
|
||
|
||
a = lookup_attribute ("interrupt_handler", DECL_MACHINE_ATTRIBUTES (func));
|
||
if (a != NULL_TREE)
|
||
ret = 1;
|
||
|
||
else
|
||
{
|
||
a = lookup_attribute ("interrupt", DECL_MACHINE_ATTRIBUTES (func));
|
||
ret = a != NULL_TREE;
|
||
}
|
||
|
||
/* Its not safe to trust global variables until after function inlining has
|
||
been done. */
|
||
if (reload_completed | reload_in_progress)
|
||
v850_interrupt_p = ret;
|
||
|
||
return ret;
|
||
}
|
||
|
||
|
||
extern struct obstack *saveable_obstack;
|
||
|
||
v850_encode_data_area (decl)
|
||
tree decl;
|
||
{
|
||
char *str = XSTR (XEXP (DECL_RTL (decl), 0), 0);
|
||
int len = strlen (str);
|
||
char *newstr;
|
||
|
||
/* In the Cygnus sources we actually do something; this is just
|
||
here to make merges easier. */
|
||
return;
|
||
}
|
||
|
||
/* Return true if the given RTX is a register which can be restored
|
||
by a function epilogue. */
|
||
int
|
||
register_is_ok_for_epilogue (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
/* The save/restore routines can only cope with registers 2, and 20 - 31 */
|
||
return (GET_CODE (op) == REG)
|
||
&& (((REGNO (op) >= 20) && REGNO (op) <= 31)
|
||
|| REGNO (op) == 2);
|
||
}
|
||
|
||
/* Return non-zero if the given RTX is suitable for collapsing into
|
||
jump to a function epilogue. */
|
||
int
|
||
pattern_is_ok_for_epilogue (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
int count = XVECLEN (op, 0);
|
||
int i;
|
||
|
||
/* If there are no registers to restore then the function epilogue
|
||
is not suitable. */
|
||
if (count <= 2)
|
||
return 0;
|
||
|
||
/* The pattern matching has already established that we are performing a
|
||
function epilogue and that we are popping at least one register. We must
|
||
now check the remaining entries in the vector to make sure that they are
|
||
also register pops. There is no good reason why there should ever be
|
||
anything else in this vector, but being paranoid always helps...
|
||
|
||
The test below performs the C equivalent of this machine description
|
||
pattern match:
|
||
|
||
(set (match_operand:SI n "register_is_ok_for_epilogue" "r")
|
||
(mem:SI (plus:SI (reg:SI 3) (match_operand:SI n "immediate_operand" "i"))))
|
||
*/
|
||
|
||
for (i = 3; i < count; i++)
|
||
{
|
||
rtx vector_element = XVECEXP (op, 0, i);
|
||
rtx dest;
|
||
rtx src;
|
||
rtx plus;
|
||
|
||
if (GET_CODE (vector_element) != SET)
|
||
return 0;
|
||
|
||
dest = SET_DEST (vector_element);
|
||
src = SET_SRC (vector_element);
|
||
|
||
if (GET_CODE (dest) != REG
|
||
|| GET_MODE (dest) != SImode
|
||
|| ! register_is_ok_for_epilogue (dest, SImode)
|
||
|| GET_CODE (src) != MEM
|
||
|| GET_MODE (src) != SImode)
|
||
return 0;
|
||
|
||
plus = XEXP (src, 0);
|
||
|
||
if (GET_CODE (plus) != PLUS
|
||
|| GET_CODE (XEXP (plus, 0)) != REG
|
||
|| GET_MODE (XEXP (plus, 0)) != SImode
|
||
|| REGNO (XEXP (plus, 0)) != STACK_POINTER_REGNUM
|
||
|| GET_CODE (XEXP (plus, 1)) != CONST_INT)
|
||
return 0;
|
||
}
|
||
|
||
return 1;
|
||
}
|
||
|
||
/* Construct a JR instruction to a routine that will perform the equivalent of
|
||
the RTL passed in as an argument. This RTL is a function epilogue that
|
||
pops registers off the stack and possibly releases some extra stack space
|
||
as well. The code has already verified that the RTL matches these
|
||
requirements. */
|
||
char *
|
||
construct_restore_jr (op)
|
||
rtx op;
|
||
{
|
||
int count = XVECLEN (op, 0);
|
||
int stack_bytes;
|
||
unsigned long int mask;
|
||
unsigned long int first;
|
||
unsigned long int last;
|
||
int i;
|
||
static char buff [100]; /* XXX */
|
||
|
||
if (count <= 2)
|
||
{
|
||
error ("Bogus JR construction: %d\n", count);
|
||
return NULL;
|
||
}
|
||
|
||
/* Work out how many bytes to pop off the stack before retrieving
|
||
registers. */
|
||
if (GET_CODE (XVECEXP (op, 0, 1)) != SET)
|
||
abort ();
|
||
if (GET_CODE (SET_SRC (XVECEXP (op, 0, 1))) != PLUS)
|
||
abort ();
|
||
if (GET_CODE (XEXP (SET_SRC (XVECEXP (op, 0, 1)), 1)) != CONST_INT)
|
||
abort ();
|
||
|
||
stack_bytes = INTVAL (XEXP (SET_SRC (XVECEXP (op, 0, 1)), 1));
|
||
|
||
/* Each pop will remove 4 bytes from the stack... */
|
||
stack_bytes -= (count - 2) * 4;
|
||
|
||
/* Make sure that the amount we are popping either 0 or 16 bytes. */
|
||
if (stack_bytes != 0 && stack_bytes != 16)
|
||
{
|
||
error ("Bad amount of stack space removal: %d", stack_bytes);
|
||
return NULL;
|
||
}
|
||
|
||
/* Now compute the bit mask of registers to push. */
|
||
mask = 0;
|
||
for (i = 2; i < count; i++)
|
||
{
|
||
rtx vector_element = XVECEXP (op, 0, i);
|
||
|
||
if (GET_CODE (vector_element) != SET)
|
||
abort ();
|
||
if (GET_CODE (SET_DEST (vector_element)) != REG)
|
||
abort ();
|
||
if (! register_is_ok_for_epilogue (SET_DEST (vector_element), SImode))
|
||
abort ();
|
||
|
||
mask |= 1 << REGNO (SET_DEST (vector_element));
|
||
}
|
||
|
||
/* Scan for the first register to pop. */
|
||
for (first = 0; first < 32; first++)
|
||
{
|
||
if (mask & (1 << first))
|
||
break;
|
||
}
|
||
|
||
if (first >= 32)
|
||
abort ();
|
||
|
||
/* Discover the last register to pop. */
|
||
if (mask & (1 << 31))
|
||
{
|
||
if (stack_bytes != 16)
|
||
abort ();
|
||
|
||
last = 31;
|
||
}
|
||
else
|
||
{
|
||
if (stack_bytes != 0)
|
||
abort ();
|
||
if ((mask & (1 << 29)) == 0)
|
||
abort ();
|
||
|
||
last = 29;
|
||
}
|
||
|
||
/* Note, it is possible to have gaps in the register mask.
|
||
We ignore this here, and generate a JR anyway. We will
|
||
be popping more registers thatn is strictly necessary, but
|
||
it does save code space. */
|
||
|
||
if (first == last)
|
||
sprintf (buff, "jr __return_%s", reg_names [first]);
|
||
else
|
||
sprintf (buff, "jr __return_%s_%s", reg_names [first], reg_names [last]);
|
||
|
||
return buff;
|
||
}
|
||
|
||
|
||
/* Return non-zero if the given RTX is suitable for collapsing into
|
||
a jump to a function prologue. */
|
||
int
|
||
pattern_is_ok_for_prologue (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
int count = XVECLEN (op, 0);
|
||
int i;
|
||
rtx vector_element;
|
||
|
||
/* If there are no registers to save then the function prologue
|
||
is not suitable. */
|
||
if (count <= 2)
|
||
return 0;
|
||
|
||
/* The pattern matching has already established that we are adjusting the
|
||
stack and pushing at least one register. We must now check that the
|
||
remaining entries in the vector to make sure that they are also register
|
||
pushes, except for the last entry which should be a CLOBBER of r10.
|
||
|
||
The test below performs the C equivalent of this machine description
|
||
pattern match:
|
||
|
||
(set (mem:SI (plus:SI (reg:SI 3)
|
||
(match_operand:SI 2 "immediate_operand" "i")))
|
||
(match_operand:SI 3 "register_is_ok_for_epilogue" "r"))
|
||
|
||
*/
|
||
|
||
for (i = 2; i < count - 1; i++)
|
||
{
|
||
rtx dest;
|
||
rtx src;
|
||
rtx plus;
|
||
|
||
vector_element = XVECEXP (op, 0, i);
|
||
|
||
if (GET_CODE (vector_element) != SET)
|
||
return 0;
|
||
|
||
dest = SET_DEST (vector_element);
|
||
src = SET_SRC (vector_element);
|
||
|
||
if (GET_CODE (dest) != MEM
|
||
|| GET_MODE (dest) != SImode
|
||
|| GET_CODE (src) != REG
|
||
|| GET_MODE (src) != SImode
|
||
|| ! register_is_ok_for_epilogue (src, SImode))
|
||
return 0;
|
||
|
||
plus = XEXP (dest, 0);
|
||
|
||
if ( GET_CODE (plus) != PLUS
|
||
|| GET_CODE (XEXP (plus, 0)) != REG
|
||
|| GET_MODE (XEXP (plus, 0)) != SImode
|
||
|| REGNO (XEXP (plus, 0)) != STACK_POINTER_REGNUM
|
||
|| GET_CODE (XEXP (plus, 1)) != CONST_INT)
|
||
return 0;
|
||
|
||
/* If the register is being pushed somewhere other than the stack
|
||
space just acquired by the first operand then abandon this quest.
|
||
Note: the test is <= because both values are negative. */
|
||
if (INTVAL (XEXP (plus, 1))
|
||
<= INTVAL (XEXP (SET_SRC (XVECEXP (op, 0, 0)), 1)))
|
||
{
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
/* Make sure that the last entry in the vector is a clobber. */
|
||
vector_element = XVECEXP (op, 0, i);
|
||
|
||
if (GET_CODE (vector_element) != CLOBBER
|
||
|| GET_CODE (XEXP (vector_element, 0)) != REG
|
||
|| REGNO (XEXP (vector_element, 0)) != 10)
|
||
return 0;
|
||
|
||
return 1;
|
||
}
|
||
|
||
/* Construct a JARL instruction to a routine that will perform the equivalent
|
||
of the RTL passed as a parameter. This RTL is a function prologue that
|
||
saves some of the registers r20 - r31 onto the stack, and possibly acquires
|
||
some stack space as well. The code has already verified that the RTL
|
||
matches these requirements. */
|
||
char *
|
||
construct_save_jarl (op)
|
||
rtx op;
|
||
{
|
||
int count = XVECLEN (op, 0);
|
||
int stack_bytes;
|
||
unsigned long int mask;
|
||
unsigned long int first;
|
||
unsigned long int last;
|
||
int i;
|
||
static char buff [100]; /* XXX */
|
||
|
||
if (count <= 2)
|
||
{
|
||
error ("Bogus JARL construction: %d\n", count);
|
||
return NULL;
|
||
}
|
||
|
||
/* Paranoia. */
|
||
if (GET_CODE (XVECEXP (op, 0, 0)) != SET)
|
||
abort ();
|
||
if (GET_CODE (SET_SRC (XVECEXP (op, 0, 0))) != PLUS)
|
||
abort ();
|
||
if (GET_CODE (XEXP (SET_SRC (XVECEXP (op, 0, 0)), 0)) != REG)
|
||
abort ();
|
||
if (GET_CODE (XEXP (SET_SRC (XVECEXP (op, 0, 0)), 1)) != CONST_INT)
|
||
abort ();
|
||
|
||
/* Work out how many bytes to push onto the stack after storing the
|
||
registers. */
|
||
stack_bytes = INTVAL (XEXP (SET_SRC (XVECEXP (op, 0, 0)), 1));
|
||
|
||
/* Each push will put 4 bytes from the stack... */
|
||
stack_bytes += (count - 2) * 4;
|
||
|
||
/* Make sure that the amount we are popping either 0 or 16 bytes. */
|
||
if (stack_bytes != 0 && stack_bytes != -16)
|
||
{
|
||
error ("Bad amount of stack space removal: %d", stack_bytes);
|
||
return NULL;
|
||
}
|
||
|
||
/* Now compute the bit mask of registers to push. */
|
||
mask = 0;
|
||
for (i = 1; i < count - 1; i++)
|
||
{
|
||
rtx vector_element = XVECEXP (op, 0, i);
|
||
|
||
if (GET_CODE (vector_element) != SET)
|
||
abort ();
|
||
if (GET_CODE (SET_SRC (vector_element)) != REG)
|
||
abort ();
|
||
if (! register_is_ok_for_epilogue (SET_SRC (vector_element), SImode))
|
||
abort ();
|
||
|
||
mask |= 1 << REGNO (SET_SRC (vector_element));
|
||
}
|
||
|
||
/* Scan for the first register to push. */
|
||
for (first = 0; first < 32; first++)
|
||
{
|
||
if (mask & (1 << first))
|
||
break;
|
||
}
|
||
|
||
if (first >= 32)
|
||
abort ();
|
||
|
||
/* Discover the last register to push. */
|
||
if (mask & (1 << 31))
|
||
{
|
||
if (stack_bytes != -16)
|
||
abort ();
|
||
|
||
last = 31;
|
||
}
|
||
else
|
||
{
|
||
if (stack_bytes != 0)
|
||
abort ();
|
||
if ((mask & (1 << 29)) == 0)
|
||
abort ();
|
||
|
||
last = 29;
|
||
}
|
||
|
||
/* Note, it is possible to have gaps in the register mask.
|
||
We ignore this here, and generate a JARL anyway. We will
|
||
be pushing more registers thatn is strictly necessary, but
|
||
it does save code space. */
|
||
|
||
if (first == last)
|
||
sprintf (buff, "jarl __save_%s, r10", reg_names [first]);
|
||
else
|
||
sprintf (buff, "jarl __save_%s_%s, r10", reg_names [first],
|
||
reg_names [last]);
|
||
|
||
return buff;
|
||
}
|
||
|