mirror of git://gcc.gnu.org/git/gcc.git
2035 lines
58 KiB
C
2035 lines
58 KiB
C
/* Xstormy16 target functions.
|
||
Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002
|
||
Free Software Foundation, Inc.
|
||
Contributed by Red Hat, Inc.
|
||
|
||
This file is part of GNU CC.
|
||
|
||
GNU CC is free software; you can redistribute it and/or modify
|
||
it under the terms of the GNU General Public License as published by
|
||
the Free Software Foundation; either version 2, or (at your option)
|
||
any later version.
|
||
|
||
GNU CC is distributed in the hope that it will be useful,
|
||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
GNU General Public License for more details.
|
||
|
||
You should have received a copy of the GNU General Public License
|
||
along with GNU CC; see the file COPYING. If not, write to
|
||
the Free Software Foundation, 59 Temple Place - Suite 330,
|
||
Boston, MA 02111-1307, USA. */
|
||
|
||
#include "config.h"
|
||
#include "system.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 "toplev.h"
|
||
#include "obstack.h"
|
||
#include "tree.h"
|
||
#include "expr.h"
|
||
#include "optabs.h"
|
||
#include "output.h"
|
||
#include "except.h"
|
||
#include "function.h"
|
||
#include "target.h"
|
||
#include "target-def.h"
|
||
#include "tm_p.h"
|
||
#include "langhooks.h"
|
||
|
||
static rtx emit_addhi3_postreload PARAMS ((rtx, rtx, rtx));
|
||
static void xstormy16_asm_out_constructor PARAMS ((rtx, int));
|
||
static void xstormy16_asm_out_destructor PARAMS ((rtx, int));
|
||
static void xstormy16_encode_section_info PARAMS ((tree, int));
|
||
|
||
/* Define the information needed to generate branch and scc insns. This is
|
||
stored from the compare operation. */
|
||
struct rtx_def * xstormy16_compare_op0;
|
||
struct rtx_def * xstormy16_compare_op1;
|
||
|
||
/* Return 1 if this is a LT, GE, LTU, or GEU operator. */
|
||
|
||
int
|
||
xstormy16_ineqsi_operator (op, mode)
|
||
register rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
enum rtx_code code = GET_CODE (op);
|
||
|
||
return ((mode == VOIDmode || GET_MODE (op) == mode)
|
||
&& (code == LT || code == GE || code == LTU || code == GEU));
|
||
}
|
||
|
||
/* Return 1 if this is an EQ or NE operator. */
|
||
|
||
int
|
||
equality_operator (op, mode)
|
||
register rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
return ((mode == VOIDmode || GET_MODE (op) == mode)
|
||
&& (GET_CODE (op) == EQ || GET_CODE (op) == NE));
|
||
}
|
||
|
||
/* Return 1 if this is a comparison operator but not an EQ or NE operator. */
|
||
|
||
int
|
||
inequality_operator (op, mode)
|
||
register rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
return comparison_operator (op, mode) && ! equality_operator (op, mode);
|
||
}
|
||
|
||
/* Branches are handled as follows:
|
||
|
||
1. HImode compare-and-branches. The machine supports these
|
||
natively, so the appropriate pattern is emitted directly.
|
||
|
||
2. SImode EQ and NE. These are emitted as pairs of HImode
|
||
compare-and-branches.
|
||
|
||
3. SImode LT, GE, LTU and GEU. These are emitted as a sequence
|
||
of a SImode subtract followed by a branch (not a compare-and-branch),
|
||
like this:
|
||
sub
|
||
sbc
|
||
blt
|
||
|
||
4. SImode GT, LE, GTU, LEU. These are emitted as a sequence like:
|
||
sub
|
||
sbc
|
||
blt
|
||
or
|
||
bne
|
||
*/
|
||
|
||
/* Emit a branch of kind CODE to location LOC. */
|
||
|
||
void
|
||
xstormy16_emit_cbranch (code, loc)
|
||
enum rtx_code code;
|
||
rtx loc;
|
||
{
|
||
rtx op0 = xstormy16_compare_op0;
|
||
rtx op1 = xstormy16_compare_op1;
|
||
rtx condition_rtx, loc_ref, branch, cy_clobber;
|
||
rtvec vec;
|
||
enum machine_mode mode;
|
||
|
||
mode = GET_MODE (op0);
|
||
if (mode != HImode && mode != SImode)
|
||
abort ();
|
||
|
||
if (mode == SImode
|
||
&& (code == GT || code == LE || code == GTU || code == LEU))
|
||
{
|
||
int unsigned_p = (code == GTU || code == LEU);
|
||
int gt_p = (code == GT || code == GTU);
|
||
rtx lab = NULL_RTX;
|
||
|
||
if (gt_p)
|
||
lab = gen_label_rtx ();
|
||
xstormy16_emit_cbranch (unsigned_p ? LTU : LT, gt_p ? lab : loc);
|
||
/* This should be generated as a comparison against the temporary
|
||
created by the previous insn, but reload can't handle that. */
|
||
xstormy16_emit_cbranch (gt_p ? NE : EQ, loc);
|
||
if (gt_p)
|
||
emit_label (lab);
|
||
return;
|
||
}
|
||
else if (mode == SImode
|
||
&& (code == NE || code == EQ)
|
||
&& op1 != const0_rtx)
|
||
{
|
||
rtx lab = NULL_RTX;
|
||
int num_words = GET_MODE_BITSIZE (mode) / BITS_PER_WORD;
|
||
int i;
|
||
|
||
if (code == EQ)
|
||
lab = gen_label_rtx ();
|
||
|
||
for (i = 0; i < num_words - 1; i++)
|
||
{
|
||
xstormy16_compare_op0 = simplify_gen_subreg (word_mode, op0, mode,
|
||
i * UNITS_PER_WORD);
|
||
xstormy16_compare_op1 = simplify_gen_subreg (word_mode, op1, mode,
|
||
i * UNITS_PER_WORD);
|
||
xstormy16_emit_cbranch (NE, code == EQ ? lab : loc);
|
||
}
|
||
xstormy16_compare_op0 = simplify_gen_subreg (word_mode, op0, mode,
|
||
i * UNITS_PER_WORD);
|
||
xstormy16_compare_op1 = simplify_gen_subreg (word_mode, op1, mode,
|
||
i * UNITS_PER_WORD);
|
||
xstormy16_emit_cbranch (code, loc);
|
||
|
||
if (code == EQ)
|
||
emit_label (lab);
|
||
return;
|
||
}
|
||
|
||
/* We can't allow reload to try to generate any reload after a branch,
|
||
so when some register must match we must make the temporary ourselves. */
|
||
if (mode != HImode)
|
||
{
|
||
rtx tmp;
|
||
tmp = gen_reg_rtx (mode);
|
||
emit_move_insn (tmp, op0);
|
||
op0 = tmp;
|
||
}
|
||
|
||
condition_rtx = gen_rtx (code, mode, op0, op1);
|
||
loc_ref = gen_rtx_LABEL_REF (VOIDmode, loc);
|
||
branch = gen_rtx_SET (VOIDmode, pc_rtx,
|
||
gen_rtx_IF_THEN_ELSE (VOIDmode, condition_rtx,
|
||
loc_ref, pc_rtx));
|
||
|
||
cy_clobber = gen_rtx_CLOBBER (VOIDmode, gen_rtx_SCRATCH (BImode));
|
||
|
||
if (mode == HImode)
|
||
vec = gen_rtvec (2, branch, cy_clobber);
|
||
else if (code == NE || code == EQ)
|
||
vec = gen_rtvec (2, branch, gen_rtx_CLOBBER (VOIDmode, op0));
|
||
else
|
||
{
|
||
rtx sub;
|
||
#if 0
|
||
sub = gen_rtx_SET (VOIDmode, op0, gen_rtx_MINUS (SImode, op0, op1));
|
||
#else
|
||
sub = gen_rtx_CLOBBER (SImode, op0);
|
||
#endif
|
||
vec = gen_rtvec (3, branch, sub, cy_clobber);
|
||
}
|
||
|
||
emit_jump_insn (gen_rtx_PARALLEL (VOIDmode, vec));
|
||
}
|
||
|
||
/* Take a SImode conditional branch, one of GT/LE/GTU/LEU, and split
|
||
the arithmetic operation. Most of the work is done by
|
||
xstormy16_expand_arith. */
|
||
|
||
void
|
||
xstormy16_split_cbranch (mode, label, comparison, dest, carry)
|
||
enum machine_mode mode;
|
||
rtx label;
|
||
rtx comparison;
|
||
rtx dest;
|
||
rtx carry;
|
||
{
|
||
rtx op0 = XEXP (comparison, 0);
|
||
rtx op1 = XEXP (comparison, 1);
|
||
rtx seq, last_insn;
|
||
rtx compare;
|
||
|
||
start_sequence ();
|
||
xstormy16_expand_arith (mode, COMPARE, dest, op0, op1, carry);
|
||
seq = get_insns ();
|
||
end_sequence ();
|
||
|
||
if (! INSN_P (seq))
|
||
abort ();
|
||
|
||
last_insn = seq;
|
||
while (NEXT_INSN (last_insn) != NULL_RTX)
|
||
last_insn = NEXT_INSN (last_insn);
|
||
|
||
compare = SET_SRC (XVECEXP (PATTERN (last_insn), 0, 0));
|
||
PUT_CODE (XEXP (compare, 0), GET_CODE (comparison));
|
||
XEXP (compare, 1) = gen_rtx_LABEL_REF (VOIDmode, label);
|
||
emit_insn (seq);
|
||
}
|
||
|
||
|
||
/* Return the string to output a conditional branch to LABEL, which is
|
||
the operand number of the label.
|
||
|
||
OP is the conditional expression, or NULL for branch-always.
|
||
|
||
REVERSED is non-zero if we should reverse the sense of the comparison.
|
||
|
||
INSN is the insn. */
|
||
|
||
char *
|
||
xstormy16_output_cbranch_hi (op, label, reversed, insn)
|
||
rtx op;
|
||
const char * label;
|
||
int reversed;
|
||
rtx insn;
|
||
{
|
||
static char string[64];
|
||
int need_longbranch = (op != NULL_RTX
|
||
? get_attr_length (insn) == 8
|
||
: get_attr_length (insn) == 4);
|
||
int really_reversed = reversed ^ need_longbranch;
|
||
const char *ccode;
|
||
const char *template;
|
||
const char *operands;
|
||
enum rtx_code code;
|
||
|
||
if (! op)
|
||
{
|
||
if (need_longbranch)
|
||
ccode = "jmpf";
|
||
else
|
||
ccode = "br";
|
||
sprintf (string, "%s %s", ccode, label);
|
||
return string;
|
||
}
|
||
|
||
code = GET_CODE (op);
|
||
|
||
if (GET_CODE (XEXP (op, 0)) != REG)
|
||
{
|
||
code = swap_condition (code);
|
||
operands = "%3,%2";
|
||
}
|
||
else
|
||
operands = "%2,%3";
|
||
|
||
/* Work out which way this really branches. */
|
||
if (really_reversed)
|
||
code = reverse_condition (code);
|
||
|
||
switch (code)
|
||
{
|
||
case EQ: ccode = "z"; break;
|
||
case NE: ccode = "nz"; break;
|
||
case GE: ccode = "ge"; break;
|
||
case LT: ccode = "lt"; break;
|
||
case GT: ccode = "gt"; break;
|
||
case LE: ccode = "le"; break;
|
||
case GEU: ccode = "nc"; break;
|
||
case LTU: ccode = "c"; break;
|
||
case GTU: ccode = "hi"; break;
|
||
case LEU: ccode = "ls"; break;
|
||
|
||
default:
|
||
abort ();
|
||
}
|
||
|
||
if (need_longbranch)
|
||
template = "b%s %s,.+8 | jmpf %s";
|
||
else
|
||
template = "b%s %s,%s";
|
||
sprintf (string, template, ccode, operands, label);
|
||
|
||
return string;
|
||
}
|
||
|
||
/* Return the string to output a conditional branch to LABEL, which is
|
||
the operand number of the label, but suitable for the tail of a
|
||
SImode branch.
|
||
|
||
OP is the conditional expression (OP is never NULL_RTX).
|
||
|
||
REVERSED is non-zero if we should reverse the sense of the comparison.
|
||
|
||
INSN is the insn. */
|
||
|
||
char *
|
||
xstormy16_output_cbranch_si (op, label, reversed, insn)
|
||
rtx op;
|
||
const char * label;
|
||
int reversed;
|
||
rtx insn;
|
||
{
|
||
static char string[64];
|
||
int need_longbranch = get_attr_length (insn) >= 8;
|
||
int really_reversed = reversed ^ need_longbranch;
|
||
const char *ccode;
|
||
const char *template;
|
||
char prevop[16];
|
||
enum rtx_code code;
|
||
|
||
code = GET_CODE (op);
|
||
|
||
/* Work out which way this really branches. */
|
||
if (really_reversed)
|
||
code = reverse_condition (code);
|
||
|
||
switch (code)
|
||
{
|
||
case EQ: ccode = "z"; break;
|
||
case NE: ccode = "nz"; break;
|
||
case GE: ccode = "ge"; break;
|
||
case LT: ccode = "lt"; break;
|
||
case GEU: ccode = "nc"; break;
|
||
case LTU: ccode = "c"; break;
|
||
|
||
/* The missing codes above should never be generated. */
|
||
default:
|
||
abort ();
|
||
}
|
||
|
||
switch (code)
|
||
{
|
||
case EQ: case NE:
|
||
{
|
||
int regnum;
|
||
|
||
if (GET_CODE (XEXP (op, 0)) != REG)
|
||
abort ();
|
||
|
||
regnum = REGNO (XEXP (op, 0));
|
||
sprintf (prevop, "or %s,%s", reg_names[regnum], reg_names[regnum+1]);
|
||
}
|
||
break;
|
||
|
||
case GE: case LT: case GEU: case LTU:
|
||
strcpy (prevop, "sbc %2,%3");
|
||
break;
|
||
|
||
default:
|
||
abort ();
|
||
}
|
||
|
||
if (need_longbranch)
|
||
template = "%s | b%s .+6 | jmpf %s";
|
||
else
|
||
template = "%s | b%s %s";
|
||
sprintf (string, template, prevop, ccode, label);
|
||
|
||
return string;
|
||
}
|
||
|
||
/* Many machines have some registers that cannot be copied directly to or from
|
||
memory or even from other types of registers. An example is the `MQ'
|
||
register, which on most machines, can only be copied to or from general
|
||
registers, but not memory. Some machines allow copying all registers to and
|
||
from memory, but require a scratch register for stores to some memory
|
||
locations (e.g., those with symbolic address on the RT, and those with
|
||
certain symbolic address on the Sparc when compiling PIC). In some cases,
|
||
both an intermediate and a scratch register are required.
|
||
|
||
You should define these macros to indicate to the reload phase that it may
|
||
need to allocate at least one register for a reload in addition to the
|
||
register to contain the data. Specifically, if copying X to a register
|
||
CLASS in MODE requires an intermediate register, you should define
|
||
`SECONDARY_INPUT_RELOAD_CLASS' to return the largest register class all of
|
||
whose registers can be used as intermediate registers or scratch registers.
|
||
|
||
If copying a register CLASS in MODE to X requires an intermediate or scratch
|
||
register, `SECONDARY_OUTPUT_RELOAD_CLASS' should be defined to return the
|
||
largest register class required. If the requirements for input and output
|
||
reloads are the same, the macro `SECONDARY_RELOAD_CLASS' should be used
|
||
instead of defining both macros identically.
|
||
|
||
The values returned by these macros are often `GENERAL_REGS'. Return
|
||
`NO_REGS' if no spare register is needed; i.e., if X can be directly copied
|
||
to or from a register of CLASS in MODE without requiring a scratch register.
|
||
Do not define this macro if it would always return `NO_REGS'.
|
||
|
||
If a scratch register is required (either with or without an intermediate
|
||
register), you should define patterns for `reload_inM' or `reload_outM', as
|
||
required.. These patterns, which will normally be implemented with a
|
||
`define_expand', should be similar to the `movM' patterns, except that
|
||
operand 2 is the scratch register.
|
||
|
||
Define constraints for the reload register and scratch register that contain
|
||
a single register class. If the original reload register (whose class is
|
||
CLASS) can meet the constraint given in the pattern, the value returned by
|
||
these macros is used for the class of the scratch register. Otherwise, two
|
||
additional reload registers are required. Their classes are obtained from
|
||
the constraints in the insn pattern.
|
||
|
||
X might be a pseudo-register or a `subreg' of a pseudo-register, which could
|
||
either be in a hard register or in memory. Use `true_regnum' to find out;
|
||
it will return -1 if the pseudo is in memory and the hard register number if
|
||
it is in a register.
|
||
|
||
These macros should not be used in the case where a particular class of
|
||
registers can only be copied to memory and not to another class of
|
||
registers. In that case, secondary reload registers are not needed and
|
||
would not be helpful. Instead, a stack location must be used to perform the
|
||
copy and the `movM' pattern should use memory as an intermediate storage.
|
||
This case often occurs between floating-point and general registers. */
|
||
|
||
enum reg_class
|
||
xstormy16_secondary_reload_class (class, mode, x)
|
||
enum reg_class class;
|
||
enum machine_mode mode;
|
||
rtx x;
|
||
{
|
||
/* This chip has the interesting property that only the first eight
|
||
registers can be moved to/from memory. */
|
||
if ((GET_CODE (x) == MEM
|
||
|| ((GET_CODE (x) == SUBREG || GET_CODE (x) == REG)
|
||
&& (true_regnum (x) == -1
|
||
|| true_regnum (x) >= FIRST_PSEUDO_REGISTER)))
|
||
&& ! reg_class_subset_p (class, EIGHT_REGS))
|
||
return EIGHT_REGS;
|
||
|
||
/* When reloading a PLUS, the carry register will be required
|
||
unless the inc or dec instructions can be used. */
|
||
if (xstormy16_carry_plus_operand (x, mode))
|
||
return CARRY_REGS;
|
||
|
||
return NO_REGS;
|
||
}
|
||
|
||
/* Recognise a PLUS that needs the carry register. */
|
||
int
|
||
xstormy16_carry_plus_operand (x, mode)
|
||
rtx x;
|
||
enum machine_mode mode ATTRIBUTE_UNUSED;
|
||
{
|
||
return (GET_CODE (x) == PLUS
|
||
&& GET_CODE (XEXP (x, 1)) == CONST_INT
|
||
&& (INTVAL (XEXP (x, 1)) < -4 || INTVAL (XEXP (x, 1)) > 4));
|
||
}
|
||
|
||
|
||
enum reg_class
|
||
xstormy16_preferred_reload_class (x, class)
|
||
enum reg_class class;
|
||
rtx x;
|
||
{
|
||
if (class == GENERAL_REGS
|
||
&& GET_CODE (x) == MEM)
|
||
return EIGHT_REGS;
|
||
|
||
return class;
|
||
}
|
||
|
||
#define LEGITIMATE_ADDRESS_INTEGER_P(X, OFFSET) \
|
||
(GET_CODE (X) == CONST_INT \
|
||
&& (unsigned HOST_WIDE_INT) (INTVAL (X) + (OFFSET) + 2048) < 4096)
|
||
|
||
#define LEGITIMATE_ADDRESS_CONST_INT_P(X, OFFSET) \
|
||
(GET_CODE (X) == CONST_INT \
|
||
&& INTVAL (X) + (OFFSET) >= 0 \
|
||
&& INTVAL (X) + (OFFSET) < 0x8000 \
|
||
&& (INTVAL (X) + (OFFSET) < 0x100 || INTVAL (X) + (OFFSET) >= 0x7F00))
|
||
|
||
int
|
||
xstormy16_legitimate_address_p (mode, x, strict)
|
||
enum machine_mode mode ATTRIBUTE_UNUSED;
|
||
rtx x;
|
||
int strict;
|
||
{
|
||
if (LEGITIMATE_ADDRESS_CONST_INT_P (x, 0))
|
||
return 1;
|
||
|
||
if (GET_CODE (x) == PLUS
|
||
&& LEGITIMATE_ADDRESS_INTEGER_P (XEXP (x, 1), 0))
|
||
x = XEXP (x, 0);
|
||
|
||
if (GET_CODE (x) == POST_INC
|
||
|| GET_CODE (x) == PRE_DEC)
|
||
x = XEXP (x, 0);
|
||
|
||
if (GET_CODE (x) == REG && REGNO_OK_FOR_BASE_P (REGNO (x))
|
||
&& (! strict || REGNO (x) < FIRST_PSEUDO_REGISTER))
|
||
return 1;
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* Return nonzero if memory address X (an RTX) can have different
|
||
meanings depending on the machine mode of the memory reference it
|
||
is used for or if the address is valid for some modes but not
|
||
others.
|
||
|
||
Autoincrement and autodecrement addresses typically have mode-dependent
|
||
effects because the amount of the increment or decrement is the size of the
|
||
operand being addressed. Some machines have other mode-dependent addresses.
|
||
Many RISC machines have no mode-dependent addresses.
|
||
|
||
You may assume that ADDR is a valid address for the machine.
|
||
|
||
On this chip, this is true if the address is valid with an offset
|
||
of 0 but not of 6, because in that case it cannot be used as an
|
||
address for DImode or DFmode, or if the address is a post-increment
|
||
or pre-decrement address. */
|
||
int
|
||
xstormy16_mode_dependent_address_p (x)
|
||
rtx x;
|
||
{
|
||
if (LEGITIMATE_ADDRESS_CONST_INT_P (x, 0)
|
||
&& ! LEGITIMATE_ADDRESS_CONST_INT_P (x, 6))
|
||
return 1;
|
||
|
||
if (GET_CODE (x) == PLUS
|
||
&& LEGITIMATE_ADDRESS_INTEGER_P (XEXP (x, 1), 0)
|
||
&& ! LEGITIMATE_ADDRESS_INTEGER_P (XEXP (x, 1), 6))
|
||
return 1;
|
||
|
||
if (GET_CODE (x) == PLUS)
|
||
x = XEXP (x, 0);
|
||
|
||
if (GET_CODE (x) == POST_INC
|
||
|| GET_CODE (x) == PRE_DEC)
|
||
return 1;
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* A C expression that defines the optional machine-dependent constraint
|
||
letters (`Q', `R', `S', `T', `U') that can be used to segregate specific
|
||
types of operands, usually memory references, for the target machine.
|
||
Normally this macro will not be defined. If it is required for a particular
|
||
target machine, it should return 1 if VALUE corresponds to the operand type
|
||
represented by the constraint letter C. If C is not defined as an extra
|
||
constraint, the value returned should be 0 regardless of VALUE. */
|
||
int
|
||
xstormy16_extra_constraint_p (x, c)
|
||
rtx x;
|
||
int c;
|
||
{
|
||
switch (c)
|
||
{
|
||
/* 'Q' is for pushes. */
|
||
case 'Q':
|
||
return (GET_CODE (x) == MEM
|
||
&& GET_CODE (XEXP (x, 0)) == POST_INC
|
||
&& XEXP (XEXP (x, 0), 0) == stack_pointer_rtx);
|
||
|
||
/* 'R' is for pops. */
|
||
case 'R':
|
||
return (GET_CODE (x) == MEM
|
||
&& GET_CODE (XEXP (x, 0)) == PRE_DEC
|
||
&& XEXP (XEXP (x, 0), 0) == stack_pointer_rtx);
|
||
|
||
/* 'S' is for immediate memory addresses. */
|
||
case 'S':
|
||
return (GET_CODE (x) == MEM
|
||
&& GET_CODE (XEXP (x, 0)) == CONST_INT
|
||
&& xstormy16_legitimate_address_p (VOIDmode, XEXP (x, 0), 0));
|
||
|
||
/* 'T' is for Rx. */
|
||
case 'T':
|
||
/* Not implemented yet. */
|
||
return 0;
|
||
|
||
/* 'U' is for CONST_INT values not between 2 and 15 inclusive,
|
||
for allocating a scratch register for 32-bit shifts. */
|
||
case 'U':
|
||
return (GET_CODE (x) == CONST_INT
|
||
&& (INTVAL (x) < 2 || INTVAL (x) > 15));
|
||
|
||
default:
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
int
|
||
short_memory_operand (x, mode)
|
||
rtx x;
|
||
enum machine_mode mode;
|
||
{
|
||
if (! memory_operand (x, mode))
|
||
return 0;
|
||
return (GET_CODE (XEXP (x, 0)) != PLUS);
|
||
}
|
||
|
||
int
|
||
nonimmediate_nonstack_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
/* 'Q' is for pushes, 'R' for pops. */
|
||
return (nonimmediate_operand (op, mode)
|
||
&& ! xstormy16_extra_constraint_p (op, 'Q')
|
||
&& ! xstormy16_extra_constraint_p (op, 'R'));
|
||
}
|
||
|
||
/* Splitter for the 'move' patterns, for modes not directly implemeted
|
||
by hardware. Emit insns to copy a value of mode MODE from SRC to
|
||
DEST.
|
||
|
||
This function is only called when reload_completed.
|
||
*/
|
||
|
||
void
|
||
xstormy16_split_move (mode, dest, src)
|
||
enum machine_mode mode;
|
||
rtx dest;
|
||
rtx src;
|
||
{
|
||
int num_words = GET_MODE_BITSIZE (mode) / BITS_PER_WORD;
|
||
int direction, end, i;
|
||
int src_modifies = 0;
|
||
int dest_modifies = 0;
|
||
int src_volatile = 0;
|
||
int dest_volatile = 0;
|
||
rtx mem_operand;
|
||
rtx auto_inc_reg_rtx = NULL_RTX;
|
||
|
||
/* Check initial conditions. */
|
||
if (! reload_completed
|
||
|| mode == QImode || mode == HImode
|
||
|| ! nonimmediate_operand (dest, mode)
|
||
|| ! general_operand (src, mode))
|
||
abort ();
|
||
|
||
/* This case is not supported below, and shouldn't be generated. */
|
||
if (GET_CODE (dest) == MEM
|
||
&& GET_CODE (src) == MEM)
|
||
abort ();
|
||
|
||
/* This case is very very bad after reload, so trap it now. */
|
||
if (GET_CODE (dest) == SUBREG
|
||
|| GET_CODE (src) == SUBREG)
|
||
abort ();
|
||
|
||
/* The general idea is to copy by words, offsetting the source and
|
||
destination. Normally the least-significant word will be copied
|
||
first, but for pre-dec operations it's better to copy the
|
||
most-significant word first. Only one operand can be a pre-dec
|
||
or post-inc operand.
|
||
|
||
It's also possible that the copy overlaps so that the direction
|
||
must be reversed. */
|
||
direction = 1;
|
||
|
||
if (GET_CODE (dest) == MEM)
|
||
{
|
||
mem_operand = XEXP (dest, 0);
|
||
dest_modifies = side_effects_p (mem_operand);
|
||
if (auto_inc_p (mem_operand))
|
||
auto_inc_reg_rtx = XEXP (mem_operand, 0);
|
||
dest_volatile = MEM_VOLATILE_P (dest);
|
||
if (dest_volatile)
|
||
{
|
||
dest = copy_rtx (dest);
|
||
MEM_VOLATILE_P (dest) = 0;
|
||
}
|
||
}
|
||
else if (GET_CODE (src) == MEM)
|
||
{
|
||
mem_operand = XEXP (src, 0);
|
||
src_modifies = side_effects_p (mem_operand);
|
||
if (auto_inc_p (mem_operand))
|
||
auto_inc_reg_rtx = XEXP (mem_operand, 0);
|
||
src_volatile = MEM_VOLATILE_P (src);
|
||
if (src_volatile)
|
||
{
|
||
src = copy_rtx (src);
|
||
MEM_VOLATILE_P (src) = 0;
|
||
}
|
||
}
|
||
else
|
||
mem_operand = NULL_RTX;
|
||
|
||
if (mem_operand == NULL_RTX)
|
||
{
|
||
if (GET_CODE (src) == REG
|
||
&& GET_CODE (dest) == REG
|
||
&& reg_overlap_mentioned_p (dest, src)
|
||
&& REGNO (dest) > REGNO (src))
|
||
direction = -1;
|
||
}
|
||
else if (GET_CODE (mem_operand) == PRE_DEC
|
||
|| (GET_CODE (mem_operand) == PLUS
|
||
&& GET_CODE (XEXP (mem_operand, 0)) == PRE_DEC))
|
||
direction = -1;
|
||
else if (GET_CODE (src) == MEM
|
||
&& reg_overlap_mentioned_p (dest, src))
|
||
{
|
||
int regno;
|
||
if (GET_CODE (dest) != REG)
|
||
abort ();
|
||
regno = REGNO (dest);
|
||
|
||
if (! refers_to_regno_p (regno, regno + num_words, mem_operand, 0))
|
||
abort ();
|
||
|
||
if (refers_to_regno_p (regno, regno + 1, mem_operand, 0))
|
||
direction = -1;
|
||
else if (refers_to_regno_p (regno + num_words - 1, regno + num_words,
|
||
mem_operand, 0))
|
||
direction = 1;
|
||
else
|
||
/* This means something like
|
||
(set (reg:DI r0) (mem:DI (reg:HI r1)))
|
||
which we'd need to support by doing the set of the second word
|
||
last. */
|
||
abort ();
|
||
}
|
||
|
||
end = direction < 0 ? -1 : num_words;
|
||
for (i = direction < 0 ? num_words - 1 : 0; i != end; i += direction)
|
||
{
|
||
rtx w_src, w_dest, insn;
|
||
|
||
if (src_modifies)
|
||
w_src = gen_rtx_MEM (word_mode, mem_operand);
|
||
else
|
||
w_src = simplify_gen_subreg (word_mode, src, mode, i * UNITS_PER_WORD);
|
||
if (src_volatile)
|
||
MEM_VOLATILE_P (w_src) = 1;
|
||
if (dest_modifies)
|
||
w_dest = gen_rtx_MEM (word_mode, mem_operand);
|
||
else
|
||
w_dest = simplify_gen_subreg (word_mode, dest, mode,
|
||
i * UNITS_PER_WORD);
|
||
if (dest_volatile)
|
||
MEM_VOLATILE_P (w_dest) = 1;
|
||
|
||
/* The simplify_subreg calls must always be able to simplify. */
|
||
if (GET_CODE (w_src) == SUBREG
|
||
|| GET_CODE (w_dest) == SUBREG)
|
||
abort ();
|
||
|
||
insn = emit_insn (gen_rtx_SET (VOIDmode, w_dest, w_src));
|
||
if (auto_inc_reg_rtx)
|
||
REG_NOTES (insn) = alloc_EXPR_LIST (REG_INC,
|
||
auto_inc_reg_rtx,
|
||
REG_NOTES (insn));
|
||
}
|
||
}
|
||
|
||
/* Expander for the 'move' patterns. Emit insns to copy a value of
|
||
mode MODE from SRC to DEST. */
|
||
|
||
void
|
||
xstormy16_expand_move (mode, dest, src)
|
||
enum machine_mode mode;
|
||
rtx dest;
|
||
rtx src;
|
||
{
|
||
/* There are only limited immediate-to-memory move instructions. */
|
||
if (! reload_in_progress
|
||
&& ! reload_completed
|
||
&& GET_CODE (dest) == MEM
|
||
&& (GET_CODE (XEXP (dest, 0)) != CONST_INT
|
||
|| ! xstormy16_legitimate_address_p (mode, XEXP (dest, 0), 0))
|
||
&& GET_CODE (src) != REG
|
||
&& GET_CODE (src) != SUBREG)
|
||
src = copy_to_mode_reg (mode, src);
|
||
|
||
/* Don't emit something we would immediately split. */
|
||
if (reload_completed
|
||
&& mode != HImode && mode != QImode)
|
||
{
|
||
xstormy16_split_move (mode, dest, src);
|
||
return;
|
||
}
|
||
|
||
emit_insn (gen_rtx_SET (VOIDmode, dest, src));
|
||
}
|
||
|
||
|
||
/* Stack Layout:
|
||
|
||
The stack is laid out as follows:
|
||
|
||
SP->
|
||
FP-> Local variables
|
||
Register save area (up to 4 words)
|
||
Argument register save area for stdarg (NUM_ARGUMENT_REGISTERS words)
|
||
|
||
AP-> Return address (two words)
|
||
9th procedure parameter word
|
||
10th procedure parameter word
|
||
...
|
||
last procedure parameter word
|
||
|
||
The frame pointer location is tuned to make it most likely that all
|
||
parameters and local variables can be accessed using a load-indexed
|
||
instruction. */
|
||
|
||
/* A structure to describe the layout. */
|
||
struct xstormy16_stack_layout
|
||
{
|
||
/* Size of the topmost three items on the stack. */
|
||
int locals_size;
|
||
int register_save_size;
|
||
int stdarg_save_size;
|
||
/* Sum of the above items. */
|
||
int frame_size;
|
||
/* Various offsets. */
|
||
int first_local_minus_ap;
|
||
int sp_minus_fp;
|
||
int fp_minus_ap;
|
||
};
|
||
|
||
/* Does REGNO need to be saved? */
|
||
#define REG_NEEDS_SAVE(REGNUM, IFUN) \
|
||
((regs_ever_live[REGNUM] && ! call_used_regs[REGNUM]) \
|
||
|| (IFUN && ! fixed_regs[REGNUM] && call_used_regs[REGNUM] \
|
||
&& (regs_ever_live[REGNUM] || ! current_function_is_leaf)))
|
||
|
||
/* Compute the stack layout. */
|
||
struct xstormy16_stack_layout
|
||
xstormy16_compute_stack_layout ()
|
||
{
|
||
struct xstormy16_stack_layout layout;
|
||
int regno;
|
||
const int ifun = xstormy16_interrupt_function_p ();
|
||
|
||
layout.locals_size = get_frame_size ();
|
||
|
||
layout.register_save_size = 0;
|
||
for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++)
|
||
if (REG_NEEDS_SAVE (regno, ifun))
|
||
layout.register_save_size += UNITS_PER_WORD;
|
||
|
||
if (current_function_stdarg)
|
||
layout.stdarg_save_size = NUM_ARGUMENT_REGISTERS * UNITS_PER_WORD;
|
||
else
|
||
layout.stdarg_save_size = 0;
|
||
|
||
layout.frame_size = (layout.locals_size
|
||
+ layout.register_save_size
|
||
+ layout.stdarg_save_size);
|
||
|
||
if (current_function_args_size <= 2048 && current_function_args_size != -1)
|
||
{
|
||
if (layout.frame_size + INCOMING_FRAME_SP_OFFSET
|
||
+ current_function_args_size <= 2048)
|
||
layout.fp_minus_ap = layout.frame_size + INCOMING_FRAME_SP_OFFSET;
|
||
else
|
||
layout.fp_minus_ap = 2048 - current_function_args_size;
|
||
}
|
||
else
|
||
layout.fp_minus_ap = (layout.stdarg_save_size
|
||
+ layout.register_save_size
|
||
+ INCOMING_FRAME_SP_OFFSET);
|
||
layout.sp_minus_fp = (layout.frame_size + INCOMING_FRAME_SP_OFFSET
|
||
- layout.fp_minus_ap);
|
||
layout.first_local_minus_ap = layout.sp_minus_fp - layout.locals_size;
|
||
return layout;
|
||
}
|
||
|
||
/* Determine how all the special registers get eliminated. */
|
||
int
|
||
xstormy16_initial_elimination_offset (from, to)
|
||
int from, to;
|
||
{
|
||
struct xstormy16_stack_layout layout;
|
||
int result;
|
||
|
||
layout = xstormy16_compute_stack_layout ();
|
||
|
||
if (from == FRAME_POINTER_REGNUM && to == HARD_FRAME_POINTER_REGNUM)
|
||
result = layout.sp_minus_fp - layout.locals_size;
|
||
else if (from == FRAME_POINTER_REGNUM && to == STACK_POINTER_REGNUM)
|
||
result = -layout.locals_size;
|
||
else if (from == ARG_POINTER_REGNUM && to == HARD_FRAME_POINTER_REGNUM)
|
||
result = -layout.fp_minus_ap;
|
||
else if (from == ARG_POINTER_REGNUM && to == STACK_POINTER_REGNUM)
|
||
result = -(layout.sp_minus_fp + layout.fp_minus_ap);
|
||
else
|
||
abort ();
|
||
|
||
return result;
|
||
}
|
||
|
||
static rtx
|
||
emit_addhi3_postreload (dest, src0, src1)
|
||
rtx dest;
|
||
rtx src0;
|
||
rtx src1;
|
||
{
|
||
rtx set, clobber, insn;
|
||
|
||
set = gen_rtx_SET (VOIDmode, dest, gen_rtx_PLUS (HImode, src0, src1));
|
||
clobber = gen_rtx_CLOBBER (VOIDmode, gen_rtx_REG (BImode, 16));
|
||
insn = emit_insn (gen_rtx_PARALLEL (VOIDmode, gen_rtvec (2, set, clobber)));
|
||
return insn;
|
||
}
|
||
|
||
/* Called after register allocation to add any instructions needed for
|
||
the prologue. Using a prologue insn is favored compared to putting
|
||
all of the instructions in the TARGET_ASM_FUNCTION_PROLOGUE macro,
|
||
since it allows the scheduler to intermix instructions with the
|
||
saves of the caller saved registers. In some cases, it might be
|
||
necessary to emit a barrier instruction as the last insn to prevent
|
||
such scheduling.
|
||
|
||
Also any insns generated here should have RTX_FRAME_RELATED_P(insn) = 1
|
||
so that the debug info generation code can handle them properly. */
|
||
void
|
||
xstormy16_expand_prologue ()
|
||
{
|
||
struct xstormy16_stack_layout layout;
|
||
int regno;
|
||
rtx insn;
|
||
rtx mem_push_rtx;
|
||
rtx mem_fake_push_rtx;
|
||
const int ifun = xstormy16_interrupt_function_p ();
|
||
|
||
mem_push_rtx = gen_rtx_POST_INC (Pmode, stack_pointer_rtx);
|
||
mem_push_rtx = gen_rtx_MEM (HImode, mem_push_rtx);
|
||
mem_fake_push_rtx = gen_rtx_PRE_INC (Pmode, stack_pointer_rtx);
|
||
mem_fake_push_rtx = gen_rtx_MEM (HImode, mem_fake_push_rtx);
|
||
|
||
layout = xstormy16_compute_stack_layout ();
|
||
|
||
/* Save the argument registers if necessary. */
|
||
if (layout.stdarg_save_size)
|
||
for (regno = FIRST_ARGUMENT_REGISTER;
|
||
regno < FIRST_ARGUMENT_REGISTER + NUM_ARGUMENT_REGISTERS;
|
||
regno++)
|
||
{
|
||
rtx reg = gen_rtx_REG (HImode, regno);
|
||
insn = emit_move_insn (mem_push_rtx, reg);
|
||
RTX_FRAME_RELATED_P (insn) = 1;
|
||
REG_NOTES (insn) = gen_rtx_EXPR_LIST (REG_FRAME_RELATED_EXPR,
|
||
gen_rtx_SET (VOIDmode,
|
||
mem_fake_push_rtx,
|
||
reg),
|
||
REG_NOTES (insn));
|
||
}
|
||
|
||
/* Push each of the registers to save. */
|
||
for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++)
|
||
if (REG_NEEDS_SAVE (regno, ifun))
|
||
{
|
||
rtx reg = gen_rtx_REG (HImode, regno);
|
||
insn = emit_move_insn (mem_push_rtx, reg);
|
||
RTX_FRAME_RELATED_P (insn) = 1;
|
||
REG_NOTES (insn) = gen_rtx_EXPR_LIST (REG_FRAME_RELATED_EXPR,
|
||
gen_rtx_SET (VOIDmode,
|
||
mem_fake_push_rtx,
|
||
reg),
|
||
REG_NOTES (insn));
|
||
}
|
||
|
||
/* It's just possible that the SP here might be what we need for
|
||
the new FP... */
|
||
if (frame_pointer_needed && layout.sp_minus_fp == layout.locals_size)
|
||
{
|
||
insn = emit_move_insn (hard_frame_pointer_rtx, stack_pointer_rtx);
|
||
RTX_FRAME_RELATED_P (insn) = 1;
|
||
}
|
||
|
||
/* Allocate space for local variables. */
|
||
if (layout.locals_size)
|
||
{
|
||
insn = emit_addhi3_postreload (stack_pointer_rtx, stack_pointer_rtx,
|
||
GEN_INT (layout.locals_size));
|
||
RTX_FRAME_RELATED_P (insn) = 1;
|
||
}
|
||
|
||
/* Set up the frame pointer, if required. */
|
||
if (frame_pointer_needed && layout.sp_minus_fp != layout.locals_size)
|
||
{
|
||
insn = emit_move_insn (hard_frame_pointer_rtx, stack_pointer_rtx);
|
||
RTX_FRAME_RELATED_P (insn) = 1;
|
||
if (layout.sp_minus_fp)
|
||
{
|
||
insn = emit_addhi3_postreload (hard_frame_pointer_rtx,
|
||
hard_frame_pointer_rtx,
|
||
GEN_INT (-layout.sp_minus_fp));
|
||
RTX_FRAME_RELATED_P (insn) = 1;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Do we need an epilogue at all? */
|
||
int
|
||
direct_return ()
|
||
{
|
||
return (reload_completed
|
||
&& xstormy16_compute_stack_layout ().frame_size == 0);
|
||
}
|
||
|
||
/* Called after register allocation to add any instructions needed for
|
||
the epilogue. Using an epilogue insn is favored compared to putting
|
||
all of the instructions in the TARGET_ASM_FUNCTION_PROLOGUE macro,
|
||
since it allows the scheduler to intermix instructions with the
|
||
saves of the caller saved registers. In some cases, it might be
|
||
necessary to emit a barrier instruction as the last insn to prevent
|
||
such scheduling. */
|
||
|
||
void
|
||
xstormy16_expand_epilogue ()
|
||
{
|
||
struct xstormy16_stack_layout layout;
|
||
rtx mem_pop_rtx;
|
||
int regno;
|
||
const int ifun = xstormy16_interrupt_function_p ();
|
||
|
||
mem_pop_rtx = gen_rtx_PRE_DEC (Pmode, stack_pointer_rtx);
|
||
mem_pop_rtx = gen_rtx_MEM (HImode, mem_pop_rtx);
|
||
|
||
layout = xstormy16_compute_stack_layout ();
|
||
|
||
/* Pop the stack for the locals. */
|
||
if (layout.locals_size)
|
||
{
|
||
if (frame_pointer_needed && layout.sp_minus_fp == layout.locals_size)
|
||
emit_move_insn (stack_pointer_rtx, hard_frame_pointer_rtx);
|
||
else
|
||
emit_addhi3_postreload (stack_pointer_rtx, stack_pointer_rtx,
|
||
GEN_INT (- layout.locals_size));
|
||
}
|
||
|
||
/* Restore any call-saved registers. */
|
||
for (regno = FIRST_PSEUDO_REGISTER - 1; regno >= 0; regno--)
|
||
if (REG_NEEDS_SAVE (regno, ifun))
|
||
emit_move_insn (gen_rtx_REG (HImode, regno), mem_pop_rtx);
|
||
|
||
/* Pop the stack for the stdarg save area. */
|
||
if (layout.stdarg_save_size)
|
||
emit_addhi3_postreload (stack_pointer_rtx, stack_pointer_rtx,
|
||
GEN_INT (- layout.stdarg_save_size));
|
||
|
||
/* Return. */
|
||
if (ifun)
|
||
emit_jump_insn (gen_return_internal_interrupt ());
|
||
else
|
||
emit_jump_insn (gen_return_internal ());
|
||
}
|
||
|
||
int
|
||
xstormy16_epilogue_uses (regno)
|
||
int regno;
|
||
{
|
||
if (reload_completed && call_used_regs[regno])
|
||
{
|
||
const int ifun = xstormy16_interrupt_function_p ();
|
||
return REG_NEEDS_SAVE (regno, ifun);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* Return an updated summarizer variable CUM to advance past an
|
||
argument in the argument list. The values MODE, TYPE and NAMED
|
||
describe that argument. Once this is done, the variable CUM is
|
||
suitable for analyzing the *following* argument with
|
||
`FUNCTION_ARG', etc.
|
||
|
||
This function need not do anything if the argument in question was
|
||
passed on the stack. The compiler knows how to track the amount of
|
||
stack space used for arguments without any special help. However,
|
||
it makes life easier for xstormy16_build_va_list if it does update
|
||
the word count. */
|
||
CUMULATIVE_ARGS
|
||
xstormy16_function_arg_advance (cum, mode, type, named)
|
||
CUMULATIVE_ARGS cum;
|
||
enum machine_mode mode;
|
||
tree type;
|
||
int named ATTRIBUTE_UNUSED;
|
||
{
|
||
/* If an argument would otherwise be passed partially in registers,
|
||
and partially on the stack, the whole of it is passed on the
|
||
stack. */
|
||
if (cum < NUM_ARGUMENT_REGISTERS
|
||
&& cum + XSTORMY16_WORD_SIZE (type, mode) > NUM_ARGUMENT_REGISTERS)
|
||
cum = NUM_ARGUMENT_REGISTERS;
|
||
|
||
cum += XSTORMY16_WORD_SIZE (type, mode);
|
||
|
||
return cum;
|
||
}
|
||
|
||
/* Do any needed setup for a variadic function. CUM has not been updated
|
||
for the last named argument which has type TYPE and mode MODE. */
|
||
void
|
||
xstormy16_setup_incoming_varargs (cum, int_mode, type, pretend_size)
|
||
CUMULATIVE_ARGS cum ATTRIBUTE_UNUSED;
|
||
int int_mode ATTRIBUTE_UNUSED;
|
||
tree type ATTRIBUTE_UNUSED;
|
||
int * pretend_size ATTRIBUTE_UNUSED;
|
||
{
|
||
}
|
||
|
||
/* Build the va_list type.
|
||
|
||
For this chip, va_list is a record containing a counter and a pointer.
|
||
The counter is of type 'int' and indicates how many bytes
|
||
have been used to date. The pointer indicates the stack position
|
||
for arguments that have not been passed in registers.
|
||
To keep the layout nice, the pointer is first in the structure. */
|
||
|
||
tree
|
||
xstormy16_build_va_list ()
|
||
{
|
||
tree f_1, f_2, record, type_decl;
|
||
|
||
record = (*lang_hooks.types.make_type) (RECORD_TYPE);
|
||
type_decl = build_decl (TYPE_DECL, get_identifier ("__va_list_tag"), record);
|
||
|
||
f_1 = build_decl (FIELD_DECL, get_identifier ("base"),
|
||
ptr_type_node);
|
||
f_2 = build_decl (FIELD_DECL, get_identifier ("count"),
|
||
unsigned_type_node);
|
||
|
||
DECL_FIELD_CONTEXT (f_1) = record;
|
||
DECL_FIELD_CONTEXT (f_2) = record;
|
||
|
||
TREE_CHAIN (record) = type_decl;
|
||
TYPE_NAME (record) = type_decl;
|
||
TYPE_FIELDS (record) = f_1;
|
||
TREE_CHAIN (f_1) = f_2;
|
||
|
||
layout_type (record);
|
||
|
||
return record;
|
||
}
|
||
|
||
/* Implement the stdarg/varargs va_start macro. STDARG_P is non-zero if this
|
||
is stdarg.h instead of varargs.h. VALIST is the tree of the va_list
|
||
variable to initialize. NEXTARG is the machine independent notion of the
|
||
'next' argument after the variable arguments. */
|
||
void
|
||
xstormy16_expand_builtin_va_start (valist, nextarg)
|
||
tree valist;
|
||
rtx nextarg ATTRIBUTE_UNUSED;
|
||
{
|
||
tree f_base, f_count;
|
||
tree base, count;
|
||
tree t;
|
||
|
||
if (xstormy16_interrupt_function_p ())
|
||
error ("cannot use va_start in interrupt function");
|
||
|
||
f_base = TYPE_FIELDS (va_list_type_node);
|
||
f_count = TREE_CHAIN (f_base);
|
||
|
||
base = build (COMPONENT_REF, TREE_TYPE (f_base), valist, f_base);
|
||
count = build (COMPONENT_REF, TREE_TYPE (f_count), valist, f_count);
|
||
|
||
t = make_tree (TREE_TYPE (base), virtual_incoming_args_rtx);
|
||
t = build (PLUS_EXPR, TREE_TYPE (base), t,
|
||
build_int_2 (INCOMING_FRAME_SP_OFFSET, 0));
|
||
t = build (MODIFY_EXPR, TREE_TYPE (base), base, t);
|
||
TREE_SIDE_EFFECTS (t) = 1;
|
||
expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);
|
||
|
||
t = build (MODIFY_EXPR, TREE_TYPE (count), count,
|
||
build_int_2 (current_function_args_info * UNITS_PER_WORD, 0));
|
||
TREE_SIDE_EFFECTS (t) = 1;
|
||
expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);
|
||
}
|
||
|
||
/* Implement the stdarg/varargs va_arg macro. VALIST is the variable
|
||
of type va_list as a tree, TYPE is the type passed to va_arg.
|
||
Note: This algorithm is documented in stormy-abi. */
|
||
|
||
rtx
|
||
xstormy16_expand_builtin_va_arg (valist, type)
|
||
tree valist;
|
||
tree type;
|
||
{
|
||
tree f_base, f_count;
|
||
tree base, count;
|
||
rtx count_rtx, addr_rtx, r;
|
||
rtx lab_gotaddr, lab_fromstack;
|
||
tree t;
|
||
int size, size_of_reg_args;
|
||
tree size_tree, count_plus_size;
|
||
rtx count_plus_size_rtx;
|
||
|
||
f_base = TYPE_FIELDS (va_list_type_node);
|
||
f_count = TREE_CHAIN (f_base);
|
||
|
||
base = build (COMPONENT_REF, TREE_TYPE (f_base), valist, f_base);
|
||
count = build (COMPONENT_REF, TREE_TYPE (f_count), valist, f_count);
|
||
|
||
size = PUSH_ROUNDING (int_size_in_bytes (type));
|
||
size_tree = round_up (size_in_bytes (type), UNITS_PER_WORD);
|
||
|
||
size_of_reg_args = NUM_ARGUMENT_REGISTERS * UNITS_PER_WORD;
|
||
|
||
count_rtx = expand_expr (count, NULL_RTX, HImode, EXPAND_NORMAL);
|
||
lab_gotaddr = gen_label_rtx ();
|
||
lab_fromstack = gen_label_rtx ();
|
||
addr_rtx = gen_reg_rtx (Pmode);
|
||
|
||
count_plus_size = build (PLUS_EXPR, TREE_TYPE (count), count, size_tree);
|
||
count_plus_size_rtx = expand_expr (count_plus_size, NULL_RTX, HImode, EXPAND_NORMAL);
|
||
emit_cmp_and_jump_insns (count_plus_size_rtx, GEN_INT (size_of_reg_args),
|
||
GTU, const1_rtx, HImode, 1, lab_fromstack);
|
||
|
||
t = build (PLUS_EXPR, ptr_type_node, base, count);
|
||
r = expand_expr (t, addr_rtx, Pmode, EXPAND_NORMAL);
|
||
if (r != addr_rtx)
|
||
emit_move_insn (addr_rtx, r);
|
||
|
||
emit_jump_insn (gen_jump (lab_gotaddr));
|
||
emit_barrier ();
|
||
emit_label (lab_fromstack);
|
||
|
||
/* Arguments larger than a word might need to skip over some
|
||
registers, since arguments are either passed entirely in
|
||
registers or entirely on the stack. */
|
||
if (size > 2 || size < 0)
|
||
{
|
||
rtx lab_notransition = gen_label_rtx ();
|
||
emit_cmp_and_jump_insns (count_rtx, GEN_INT (NUM_ARGUMENT_REGISTERS
|
||
* UNITS_PER_WORD),
|
||
GEU, const1_rtx, HImode, 1, lab_notransition);
|
||
|
||
t = build (MODIFY_EXPR, TREE_TYPE (count), count,
|
||
build_int_2 (NUM_ARGUMENT_REGISTERS * UNITS_PER_WORD, 0));
|
||
TREE_SIDE_EFFECTS (t) = 1;
|
||
expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);
|
||
|
||
emit_label (lab_notransition);
|
||
}
|
||
|
||
t = build (PLUS_EXPR, sizetype, size_tree,
|
||
build_int_2 ((- NUM_ARGUMENT_REGISTERS * UNITS_PER_WORD
|
||
+ INCOMING_FRAME_SP_OFFSET),
|
||
-1));
|
||
t = build (PLUS_EXPR, TREE_TYPE (count), count, fold (t));
|
||
t = build (MINUS_EXPR, TREE_TYPE (base), base, t);
|
||
r = expand_expr (t, addr_rtx, Pmode, EXPAND_NORMAL);
|
||
if (r != addr_rtx)
|
||
emit_move_insn (addr_rtx, r);
|
||
|
||
emit_label (lab_gotaddr);
|
||
|
||
count_plus_size = build (PLUS_EXPR, TREE_TYPE (count), count, size_tree);
|
||
t = build (MODIFY_EXPR, TREE_TYPE (count), count, count_plus_size);
|
||
TREE_SIDE_EFFECTS (t) = 1;
|
||
expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);
|
||
|
||
return addr_rtx;
|
||
}
|
||
|
||
/* Initialize the variable parts of a trampoline. ADDR is an RTX for
|
||
the address of the trampoline; FNADDR is an RTX for the address of
|
||
the nested function; STATIC_CHAIN is an RTX for the static chain
|
||
value that should be passed to the function when it is called. */
|
||
void
|
||
xstormy16_initialize_trampoline (addr, fnaddr, static_chain)
|
||
rtx addr;
|
||
rtx fnaddr;
|
||
rtx static_chain;
|
||
{
|
||
rtx reg_addr = gen_reg_rtx (Pmode);
|
||
rtx temp = gen_reg_rtx (HImode);
|
||
rtx reg_fnaddr = gen_reg_rtx (HImode);
|
||
rtx reg_addr_mem;
|
||
|
||
reg_addr_mem = gen_rtx_MEM (HImode, reg_addr);
|
||
|
||
emit_move_insn (reg_addr, addr);
|
||
emit_move_insn (temp, GEN_INT (0x3130 | STATIC_CHAIN_REGNUM));
|
||
emit_move_insn (reg_addr_mem, temp);
|
||
emit_insn (gen_addhi3 (reg_addr, reg_addr, const2_rtx));
|
||
emit_move_insn (temp, static_chain);
|
||
emit_move_insn (reg_addr_mem, temp);
|
||
emit_insn (gen_addhi3 (reg_addr, reg_addr, const2_rtx));
|
||
emit_move_insn (reg_fnaddr, fnaddr);
|
||
emit_move_insn (temp, reg_fnaddr);
|
||
emit_insn (gen_andhi3 (temp, temp, GEN_INT (0xFF)));
|
||
emit_insn (gen_iorhi3 (temp, temp, GEN_INT (0x0200)));
|
||
emit_move_insn (reg_addr_mem, temp);
|
||
emit_insn (gen_addhi3 (reg_addr, reg_addr, const2_rtx));
|
||
emit_insn (gen_lshrhi3 (reg_fnaddr, reg_fnaddr, GEN_INT (8)));
|
||
emit_move_insn (reg_addr_mem, reg_fnaddr);
|
||
}
|
||
|
||
/* Create an RTX representing the place where a function returns a
|
||
value of data type VALTYPE. VALTYPE is a tree node representing a
|
||
data type. Write `TYPE_MODE (VALTYPE)' to get the machine mode
|
||
used to represent that type. On many machines, only the mode is
|
||
relevant. (Actually, on most machines, scalar values are returned
|
||
in the same place regardless of mode).
|
||
|
||
If `PROMOTE_FUNCTION_RETURN' is defined, you must apply the same promotion
|
||
rules specified in `PROMOTE_MODE' if VALTYPE is a scalar type.
|
||
|
||
If the precise function being called is known, FUNC is a tree node
|
||
(`FUNCTION_DECL') for it; otherwise, FUNC is a null pointer. This makes it
|
||
possible to use a different value-returning convention for specific
|
||
functions when all their calls are known.
|
||
|
||
`FUNCTION_VALUE' is not used for return vales with aggregate data types,
|
||
because these are returned in another way. See `STRUCT_VALUE_REGNUM' and
|
||
related macros. */
|
||
rtx
|
||
xstormy16_function_value (valtype, func)
|
||
tree valtype;
|
||
tree func ATTRIBUTE_UNUSED;
|
||
{
|
||
enum machine_mode mode;
|
||
mode = TYPE_MODE (valtype);
|
||
PROMOTE_MODE (mode, 0, valtype);
|
||
return gen_rtx_REG (mode, RETURN_VALUE_REGNUM);
|
||
}
|
||
|
||
/* A C compound statement that outputs the assembler code for a thunk function,
|
||
used to implement C++ virtual function calls with multiple inheritance. The
|
||
thunk acts as a wrapper around a virtual function, adjusting the implicit
|
||
object parameter before handing control off to the real function.
|
||
|
||
First, emit code to add the integer DELTA to the location that contains the
|
||
incoming first argument. Assume that this argument contains a pointer, and
|
||
is the one used to pass the `this' pointer in C++. This is the incoming
|
||
argument *before* the function prologue, e.g. `%o0' on a sparc. The
|
||
addition must preserve the values of all other incoming arguments.
|
||
|
||
After the addition, emit code to jump to FUNCTION, which is a
|
||
`FUNCTION_DECL'. This is a direct pure jump, not a call, and does not touch
|
||
the return address. Hence returning from FUNCTION will return to whoever
|
||
called the current `thunk'.
|
||
|
||
The effect must be as if @var{function} had been called directly
|
||
with the adjusted first argument. This macro is responsible for
|
||
emitting all of the code for a thunk function;
|
||
TARGET_ASM_FUNCTION_PROLOGUE and TARGET_ASM_FUNCTION_EPILOGUE are
|
||
not invoked.
|
||
|
||
The THUNK_FNDECL is redundant. (DELTA and FUNCTION have already been
|
||
extracted from it.) It might possibly be useful on some targets, but
|
||
probably not. */
|
||
|
||
void
|
||
xstormy16_asm_output_mi_thunk (file, thunk_fndecl, delta, function)
|
||
FILE *file;
|
||
tree thunk_fndecl ATTRIBUTE_UNUSED;
|
||
int delta;
|
||
tree function;
|
||
{
|
||
int regnum = FIRST_ARGUMENT_REGISTER;
|
||
|
||
/* There might be a hidden first argument for a returned structure. */
|
||
if (aggregate_value_p (TREE_TYPE (TREE_TYPE (function))))
|
||
regnum += 1;
|
||
|
||
fprintf (file, "\tadd %s,#0x%x\n", reg_names[regnum], (delta) & 0xFFFF);
|
||
fputs ("\tjmpf ", file);
|
||
assemble_name (file, XSTR (XEXP (DECL_RTL (function), 0), 0));
|
||
putc ('\n', file);
|
||
}
|
||
|
||
/* Mark functions with SYMBOL_REF_FLAG. */
|
||
|
||
static void
|
||
xstormy16_encode_section_info (decl, first)
|
||
tree decl;
|
||
int first ATTRIBUTE_UNUSED;
|
||
{
|
||
if (TREE_CODE (decl) == FUNCTION_DECL)
|
||
SYMBOL_REF_FLAG (XEXP (DECL_RTL (decl), 0)) = 1;
|
||
}
|
||
|
||
/* Output constructors and destructors. Just like
|
||
default_named_section_asm_out_* but don't set the sections writable. */
|
||
#undef TARGET_ASM_CONSTRUCTOR
|
||
#define TARGET_ASM_CONSTRUCTOR xstormy16_asm_out_constructor
|
||
#undef TARGET_ASM_DESTRUCTOR
|
||
#define TARGET_ASM_DESTRUCTOR xstormy16_asm_out_destructor
|
||
|
||
static void
|
||
xstormy16_asm_out_destructor (symbol, priority)
|
||
rtx symbol;
|
||
int priority;
|
||
{
|
||
const char *section = ".dtors";
|
||
char buf[16];
|
||
|
||
/* ??? This only works reliably with the GNU linker. */
|
||
if (priority != DEFAULT_INIT_PRIORITY)
|
||
{
|
||
sprintf (buf, ".dtors.%.5u",
|
||
/* Invert the numbering so the linker puts us in the proper
|
||
order; constructors are run from right to left, and the
|
||
linker sorts in increasing order. */
|
||
MAX_INIT_PRIORITY - priority);
|
||
section = buf;
|
||
}
|
||
|
||
named_section_flags (section, 0);
|
||
assemble_align (POINTER_SIZE);
|
||
assemble_integer (symbol, POINTER_SIZE / BITS_PER_UNIT, POINTER_SIZE, 1);
|
||
}
|
||
|
||
static void
|
||
xstormy16_asm_out_constructor (symbol, priority)
|
||
rtx symbol;
|
||
int priority;
|
||
{
|
||
const char *section = ".ctors";
|
||
char buf[16];
|
||
|
||
/* ??? This only works reliably with the GNU linker. */
|
||
if (priority != DEFAULT_INIT_PRIORITY)
|
||
{
|
||
sprintf (buf, ".ctors.%.5u",
|
||
/* Invert the numbering so the linker puts us in the proper
|
||
order; constructors are run from right to left, and the
|
||
linker sorts in increasing order. */
|
||
MAX_INIT_PRIORITY - priority);
|
||
section = buf;
|
||
}
|
||
|
||
named_section_flags (section, 0);
|
||
assemble_align (POINTER_SIZE);
|
||
assemble_integer (symbol, POINTER_SIZE / BITS_PER_UNIT, POINTER_SIZE, 1);
|
||
}
|
||
|
||
/* Print a memory address as an operand to reference that memory location. */
|
||
void
|
||
xstormy16_print_operand_address (file, address)
|
||
FILE * file;
|
||
rtx address;
|
||
{
|
||
HOST_WIDE_INT offset;
|
||
int pre_dec, post_inc;
|
||
|
||
/* There are a few easy cases. */
|
||
if (GET_CODE (address) == CONST_INT)
|
||
{
|
||
fprintf (file, HOST_WIDE_INT_PRINT_DEC, INTVAL (address) & 0xFFFF);
|
||
return;
|
||
}
|
||
|
||
if (CONSTANT_P (address) || GET_CODE (address) == CODE_LABEL)
|
||
{
|
||
output_addr_const (file, address);
|
||
return;
|
||
}
|
||
|
||
/* Otherwise, it's hopefully something of the form
|
||
(plus:HI (pre_dec:HI (reg:HI ...)) (const_int ...))
|
||
*/
|
||
|
||
if (GET_CODE (address) == PLUS)
|
||
{
|
||
if (GET_CODE (XEXP (address, 1)) != CONST_INT)
|
||
abort ();
|
||
offset = INTVAL (XEXP (address, 1));
|
||
address = XEXP (address, 0);
|
||
}
|
||
else
|
||
offset = 0;
|
||
|
||
pre_dec = (GET_CODE (address) == PRE_DEC);
|
||
post_inc = (GET_CODE (address) == POST_INC);
|
||
if (pre_dec || post_inc)
|
||
address = XEXP (address, 0);
|
||
|
||
if (GET_CODE (address) != REG)
|
||
abort ();
|
||
|
||
fputc ('(', file);
|
||
if (pre_dec)
|
||
fputs ("--", file);
|
||
fputs (reg_names [REGNO (address)], file);
|
||
if (post_inc)
|
||
fputs ("++", file);
|
||
if (offset != 0)
|
||
{
|
||
fputc (',', file);
|
||
fprintf (file, HOST_WIDE_INT_PRINT_DEC, offset);
|
||
}
|
||
fputc (')', file);
|
||
}
|
||
|
||
/* Print an operand to an assembler instruction. */
|
||
void
|
||
xstormy16_print_operand (file, x, code)
|
||
FILE * file;
|
||
rtx x;
|
||
int code;
|
||
{
|
||
switch (code)
|
||
{
|
||
case 'B':
|
||
/* There is either one bit set, or one bit clear, in X.
|
||
Print it preceded by '#'. */
|
||
{
|
||
HOST_WIDE_INT xx = 1;
|
||
HOST_WIDE_INT l;
|
||
|
||
if (GET_CODE (x) == CONST_INT)
|
||
xx = INTVAL (x);
|
||
else
|
||
output_operand_lossage ("`B' operand is not constant");
|
||
|
||
l = exact_log2 (xx);
|
||
if (l == -1)
|
||
l = exact_log2 (~xx);
|
||
if (l == -1)
|
||
output_operand_lossage ("`B' operand has multiple bits set");
|
||
|
||
fputs (IMMEDIATE_PREFIX, file);
|
||
fprintf (file, HOST_WIDE_INT_PRINT_DEC, l);
|
||
return;
|
||
}
|
||
|
||
case 'C':
|
||
/* Print the symbol without a surrounding @fptr(). */
|
||
if (GET_CODE (x) == SYMBOL_REF)
|
||
assemble_name (file, XSTR (x, 0));
|
||
else if (GET_CODE (x) == LABEL_REF)
|
||
output_asm_label (x);
|
||
else
|
||
xstormy16_print_operand_address (file, x);
|
||
return;
|
||
|
||
case 'o':
|
||
case 'O':
|
||
/* Print the immediate operand less one, preceded by '#'.
|
||
For 'O', negate it first. */
|
||
{
|
||
HOST_WIDE_INT xx = 0;
|
||
|
||
if (GET_CODE (x) == CONST_INT)
|
||
xx = INTVAL (x);
|
||
else
|
||
output_operand_lossage ("`o' operand is not constant");
|
||
|
||
if (code == 'O')
|
||
xx = -xx;
|
||
|
||
fputs (IMMEDIATE_PREFIX, file);
|
||
fprintf (file, HOST_WIDE_INT_PRINT_DEC, xx - 1);
|
||
return;
|
||
}
|
||
|
||
case 0:
|
||
/* Handled below. */
|
||
break;
|
||
|
||
default:
|
||
output_operand_lossage ("xstormy16_print_operand: unknown code");
|
||
return;
|
||
}
|
||
|
||
switch (GET_CODE (x))
|
||
{
|
||
case REG:
|
||
fputs (reg_names [REGNO (x)], file);
|
||
break;
|
||
|
||
case MEM:
|
||
xstormy16_print_operand_address (file, XEXP (x, 0));
|
||
break;
|
||
|
||
default:
|
||
/* Some kind of constant or label; an immediate operand,
|
||
so prefix it with '#' for the assembler. */
|
||
fputs (IMMEDIATE_PREFIX, file);
|
||
output_addr_const (file, x);
|
||
break;
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
/* Expander for the `casesi' pattern.
|
||
INDEX is the index of the switch statement.
|
||
LOWER_BOUND is a CONST_INT that is the value of INDEX corresponding
|
||
to the first table entry.
|
||
RANGE is the number of table entries.
|
||
TABLE is an ADDR_VEC that is the jump table.
|
||
DEFAULT_LABEL is the address to branch to if INDEX is outside the
|
||
range LOWER_BOUND to LOWER_BOUND+RANGE-1.
|
||
*/
|
||
|
||
void
|
||
xstormy16_expand_casesi (index, lower_bound, range, table, default_label)
|
||
rtx index;
|
||
rtx lower_bound;
|
||
rtx range;
|
||
rtx table;
|
||
rtx default_label;
|
||
{
|
||
HOST_WIDE_INT range_i = INTVAL (range);
|
||
rtx int_index;
|
||
|
||
/* This code uses 'br', so it can deal only with tables of size up to
|
||
8192 entries. */
|
||
if (range_i >= 8192)
|
||
sorry ("switch statement of size %lu entries too large",
|
||
(unsigned long) range_i);
|
||
|
||
index = expand_binop (SImode, sub_optab, index, lower_bound, NULL_RTX, 0,
|
||
OPTAB_LIB_WIDEN);
|
||
emit_cmp_and_jump_insns (index, range, GTU, NULL_RTX, SImode, 1,
|
||
default_label);
|
||
int_index = gen_lowpart_common (HImode, index);
|
||
emit_insn (gen_ashlhi3 (int_index, int_index, GEN_INT (2)));
|
||
emit_jump_insn (gen_tablejump_pcrel (int_index, table));
|
||
}
|
||
|
||
/* Output an ADDR_VEC. It is output as a sequence of 'jmpf'
|
||
instructions, without label or alignment or any other special
|
||
constructs. We know that the previous instruction will be the
|
||
`tablejump_pcrel' output above.
|
||
|
||
TODO: it might be nice to output 'br' instructions if they could
|
||
all reach. */
|
||
|
||
void
|
||
xstormy16_output_addr_vec (file, label, table)
|
||
FILE *file;
|
||
rtx label ATTRIBUTE_UNUSED;
|
||
rtx table;
|
||
{
|
||
int vlen, idx;
|
||
|
||
function_section (current_function_decl);
|
||
|
||
vlen = XVECLEN (table, 0);
|
||
for (idx = 0; idx < vlen; idx++)
|
||
{
|
||
fputs ("\tjmpf ", file);
|
||
output_asm_label (XEXP (XVECEXP (table, 0, idx), 0));
|
||
fputc ('\n', file);
|
||
}
|
||
}
|
||
|
||
|
||
/* Expander for the `call' patterns.
|
||
INDEX is the index of the switch statement.
|
||
LOWER_BOUND is a CONST_INT that is the value of INDEX corresponding
|
||
to the first table entry.
|
||
RANGE is the number of table entries.
|
||
TABLE is an ADDR_VEC that is the jump table.
|
||
DEFAULT_LABEL is the address to branch to if INDEX is outside the
|
||
range LOWER_BOUND to LOWER_BOUND+RANGE-1.
|
||
*/
|
||
|
||
void
|
||
xstormy16_expand_call (retval, dest, counter)
|
||
rtx retval;
|
||
rtx dest;
|
||
rtx counter;
|
||
{
|
||
rtx call, temp;
|
||
enum machine_mode mode;
|
||
|
||
if (GET_CODE (dest) != MEM)
|
||
abort ();
|
||
dest = XEXP (dest, 0);
|
||
|
||
if (! CONSTANT_P (dest)
|
||
&& GET_CODE (dest) != REG)
|
||
dest = force_reg (Pmode, dest);
|
||
|
||
if (retval == NULL)
|
||
mode = VOIDmode;
|
||
else
|
||
mode = GET_MODE (retval);
|
||
|
||
call = gen_rtx_CALL (mode, gen_rtx_MEM (FUNCTION_MODE, dest),
|
||
counter);
|
||
if (retval)
|
||
call = gen_rtx_SET (VOIDmode, retval, call);
|
||
|
||
if (! CONSTANT_P (dest))
|
||
{
|
||
temp = gen_reg_rtx (HImode);
|
||
emit_move_insn (temp, const0_rtx);
|
||
}
|
||
else
|
||
temp = const0_rtx;
|
||
|
||
call = gen_rtx_PARALLEL (VOIDmode, gen_rtvec (2, call,
|
||
gen_rtx_USE (VOIDmode, temp)));
|
||
emit_call_insn (call);
|
||
}
|
||
|
||
/* Expanders for multiword computational operations. */
|
||
|
||
/* Expander for arithmetic operations; emit insns to compute
|
||
|
||
(set DEST (CODE:MODE SRC0 SRC1))
|
||
|
||
using CARRY as a temporary. When CODE is COMPARE, a branch
|
||
template is generated (this saves duplicating code in
|
||
xstormy16_split_cbranch). */
|
||
|
||
void
|
||
xstormy16_expand_arith (mode, code, dest, src0, src1, carry)
|
||
enum machine_mode mode;
|
||
enum rtx_code code;
|
||
rtx dest;
|
||
rtx src0;
|
||
rtx src1;
|
||
rtx carry;
|
||
{
|
||
int num_words = GET_MODE_BITSIZE (mode) / BITS_PER_WORD;
|
||
int i;
|
||
int firstloop = 1;
|
||
|
||
if (code == NEG)
|
||
{
|
||
rtx zero_reg = gen_reg_rtx (word_mode);
|
||
emit_move_insn (zero_reg, src0);
|
||
src0 = zero_reg;
|
||
}
|
||
|
||
for (i = 0; i < num_words; i++)
|
||
{
|
||
rtx w_src0, w_src1, w_dest;
|
||
rtx insn;
|
||
|
||
if (code == NEG)
|
||
w_src0 = src0;
|
||
else
|
||
w_src0 = simplify_gen_subreg (word_mode, src0, mode,
|
||
i * UNITS_PER_WORD);
|
||
w_src1 = simplify_gen_subreg (word_mode, src1, mode, i * UNITS_PER_WORD);
|
||
w_dest = simplify_gen_subreg (word_mode, dest, mode, i * UNITS_PER_WORD);
|
||
|
||
switch (code)
|
||
{
|
||
case PLUS:
|
||
if (firstloop
|
||
&& GET_CODE (w_src1) == CONST_INT && INTVAL (w_src1) == 0)
|
||
continue;
|
||
|
||
if (firstloop)
|
||
insn = gen_addchi4 (w_dest, w_src0, w_src1, carry);
|
||
else
|
||
insn = gen_addchi5 (w_dest, w_src0, w_src1, carry, carry);
|
||
break;
|
||
|
||
case NEG:
|
||
case MINUS:
|
||
case COMPARE:
|
||
if (code == COMPARE && i == num_words - 1)
|
||
{
|
||
rtx branch, sub, clobber, sub_1;
|
||
|
||
sub_1 = gen_rtx_MINUS (HImode, w_src0,
|
||
gen_rtx_ZERO_EXTEND (HImode, carry));
|
||
sub = gen_rtx_SET (VOIDmode, w_dest,
|
||
gen_rtx_MINUS (HImode, sub_1, w_src1));
|
||
clobber = gen_rtx_CLOBBER (VOIDmode, carry);
|
||
branch = gen_rtx_SET (VOIDmode, pc_rtx,
|
||
gen_rtx_IF_THEN_ELSE (VOIDmode,
|
||
gen_rtx_EQ (HImode,
|
||
sub_1,
|
||
w_src1),
|
||
pc_rtx,
|
||
pc_rtx));
|
||
insn = gen_rtx_PARALLEL (VOIDmode,
|
||
gen_rtvec (3, branch, sub, clobber));
|
||
}
|
||
else if (firstloop
|
||
&& code != COMPARE
|
||
&& GET_CODE (w_src1) == CONST_INT && INTVAL (w_src1) == 0)
|
||
continue;
|
||
else if (firstloop)
|
||
insn = gen_subchi4 (w_dest, w_src0, w_src1, carry);
|
||
else
|
||
insn = gen_subchi5 (w_dest, w_src0, w_src1, carry, carry);
|
||
break;
|
||
|
||
case IOR:
|
||
case XOR:
|
||
case AND:
|
||
if (GET_CODE (w_src1) == CONST_INT
|
||
&& INTVAL (w_src1) == -(code == AND))
|
||
continue;
|
||
|
||
insn = gen_rtx_SET (VOIDmode, w_dest, gen_rtx (code, mode,
|
||
w_src0, w_src1));
|
||
break;
|
||
|
||
case NOT:
|
||
insn = gen_rtx_SET (VOIDmode, w_dest, gen_rtx_NOT (mode, w_src0));
|
||
break;
|
||
|
||
default:
|
||
abort ();
|
||
}
|
||
|
||
firstloop = 0;
|
||
emit (insn);
|
||
}
|
||
}
|
||
|
||
/* Return 1 if OP is a shift operator. */
|
||
|
||
int
|
||
shift_operator (op, mode)
|
||
register rtx op;
|
||
enum machine_mode mode ATTRIBUTE_UNUSED;
|
||
{
|
||
enum rtx_code code = GET_CODE (op);
|
||
|
||
return (code == ASHIFT
|
||
|| code == ASHIFTRT
|
||
|| code == LSHIFTRT);
|
||
}
|
||
|
||
/* The shift operations are split at output time for constant values;
|
||
variable-width shifts get handed off to a library routine.
|
||
|
||
Generate an output string to do (set X (CODE:MODE X SIZE_R))
|
||
SIZE_R will be a CONST_INT, X will be a hard register. */
|
||
|
||
const char *
|
||
xstormy16_output_shift (mode, code, x, size_r, temp)
|
||
enum machine_mode mode;
|
||
enum rtx_code code;
|
||
rtx x;
|
||
rtx size_r;
|
||
rtx temp;
|
||
{
|
||
HOST_WIDE_INT size;
|
||
const char *r0, *r1, *rt;
|
||
static char r[64];
|
||
|
||
if (GET_CODE (size_r) != CONST_INT
|
||
|| GET_CODE (x) != REG
|
||
|| mode != SImode)
|
||
abort ();
|
||
size = INTVAL (size_r) & (GET_MODE_BITSIZE (mode) - 1);
|
||
|
||
if (size == 0)
|
||
return "";
|
||
|
||
r0 = reg_names [REGNO (x)];
|
||
r1 = reg_names [REGNO (x) + 1];
|
||
|
||
/* For shifts of size 1, we can use the rotate instructions. */
|
||
if (size == 1)
|
||
{
|
||
switch (code)
|
||
{
|
||
case ASHIFT:
|
||
sprintf (r, "shl %s,#1 | rlc %s,#1", r0, r1);
|
||
break;
|
||
case ASHIFTRT:
|
||
sprintf (r, "asr %s,#1 | rrc %s,#1", r1, r0);
|
||
break;
|
||
case LSHIFTRT:
|
||
sprintf (r, "shr %s,#1 | rrc %s,#1", r1, r0);
|
||
break;
|
||
default:
|
||
abort ();
|
||
}
|
||
return r;
|
||
}
|
||
|
||
/* For large shifts, there are easy special cases. */
|
||
if (size == 16)
|
||
{
|
||
switch (code)
|
||
{
|
||
case ASHIFT:
|
||
sprintf (r, "mov %s,%s | mov %s,#0", r1, r0, r0);
|
||
break;
|
||
case ASHIFTRT:
|
||
sprintf (r, "mov %s,%s | asr %s,#15", r0, r1, r1);
|
||
break;
|
||
case LSHIFTRT:
|
||
sprintf (r, "mov %s,%s | mov %s,#0", r0, r1, r1);
|
||
break;
|
||
default:
|
||
abort ();
|
||
}
|
||
return r;
|
||
}
|
||
if (size > 16)
|
||
{
|
||
switch (code)
|
||
{
|
||
case ASHIFT:
|
||
sprintf (r, "mov %s,%s | mov %s,#0 | shl %s,#%d",
|
||
r1, r0, r0, r1, (int) size - 16);
|
||
break;
|
||
case ASHIFTRT:
|
||
sprintf (r, "mov %s,%s | asr %s,#15 | asr %s,#%d",
|
||
r0, r1, r1, r0, (int) size - 16);
|
||
break;
|
||
case LSHIFTRT:
|
||
sprintf (r, "mov %s,%s | mov %s,#0 | shr %s,#%d",
|
||
r0, r1, r1, r0, (int) size - 16);
|
||
break;
|
||
default:
|
||
abort ();
|
||
}
|
||
return r;
|
||
}
|
||
|
||
/* For the rest, we have to do more work. In particular, we
|
||
need a temporary. */
|
||
rt = reg_names [REGNO (temp)];
|
||
switch (code)
|
||
{
|
||
case ASHIFT:
|
||
sprintf (r,
|
||
"mov %s,%s | shl %s,#%d | shl %s,#%d | shr %s,#%d | or %s,%s",
|
||
rt, r0, r0, (int) size, r1, (int) size, rt, (int) 16-size,
|
||
r1, rt);
|
||
break;
|
||
case ASHIFTRT:
|
||
sprintf (r,
|
||
"mov %s,%s | asr %s,#%d | shr %s,#%d | shl %s,#%d | or %s,%s",
|
||
rt, r1, r1, (int) size, r0, (int) size, rt, (int) 16-size,
|
||
r0, rt);
|
||
break;
|
||
case LSHIFTRT:
|
||
sprintf (r,
|
||
"mov %s,%s | shr %s,#%d | shr %s,#%d | shl %s,#%d | or %s,%s",
|
||
rt, r1, r1, (int) size, r0, (int) size, rt, (int) 16-size,
|
||
r0, rt);
|
||
break;
|
||
default:
|
||
abort ();
|
||
}
|
||
return r;
|
||
}
|
||
|
||
/* Attribute handling. */
|
||
|
||
/* Return nonzero if the function is an interrupt function. */
|
||
int
|
||
xstormy16_interrupt_function_p ()
|
||
{
|
||
tree attributes;
|
||
|
||
/* The dwarf2 mechanism asks for INCOMING_FRAME_SP_OFFSET before
|
||
any functions are declared, which is demonstrably wrong, but
|
||
it is worked around here. FIXME. */
|
||
if (!cfun)
|
||
return 0;
|
||
|
||
attributes = TYPE_ATTRIBUTES (TREE_TYPE (current_function_decl));
|
||
return lookup_attribute ("interrupt", attributes) != NULL_TREE;
|
||
}
|
||
|
||
#undef TARGET_ATTRIBUTE_TABLE
|
||
#define TARGET_ATTRIBUTE_TABLE xstormy16_attribute_table
|
||
static tree xstormy16_handle_interrupt_attribute PARAMS ((tree *, tree, tree, int, bool *));
|
||
static const struct attribute_spec xstormy16_attribute_table[] =
|
||
{
|
||
/* { name, min_len, max_len, decl_req, type_req, fn_type_req, handler } */
|
||
{ "interrupt", 0, 0, false, true, true, xstormy16_handle_interrupt_attribute },
|
||
{ NULL, 0, 0, false, false, false, NULL }
|
||
};
|
||
|
||
/* Handle an "interrupt" attribute;
|
||
arguments as in struct attribute_spec.handler. */
|
||
static tree
|
||
xstormy16_handle_interrupt_attribute (node, name, args, flags, no_add_attrs)
|
||
tree *node;
|
||
tree name;
|
||
tree args ATTRIBUTE_UNUSED;
|
||
int flags ATTRIBUTE_UNUSED;
|
||
bool *no_add_attrs;
|
||
{
|
||
if (TREE_CODE (*node) != FUNCTION_TYPE)
|
||
{
|
||
warning ("`%s' attribute only applies to functions",
|
||
IDENTIFIER_POINTER (name));
|
||
*no_add_attrs = true;
|
||
}
|
||
|
||
return NULL_TREE;
|
||
}
|
||
|
||
#undef TARGET_ASM_ALIGNED_HI_OP
|
||
#define TARGET_ASM_ALIGNED_HI_OP "\t.hword\t"
|
||
#undef TARGET_ASM_ALIGNED_SI_OP
|
||
#define TARGET_ASM_ALIGNED_SI_OP "\t.word\t"
|
||
#undef TARGET_ENCODE_SECTION_INFO
|
||
#define TARGET_ENCODE_SECTION_INFO xstormy16_encode_section_info
|
||
|
||
struct gcc_target targetm = TARGET_INITIALIZER;
|