NetBSD/gnu/dist/gcc/config/arc/arc.c

2213 lines
63 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Subroutines used for code generation on the Argonaut ARC cpu.
Copyright (C) 1994, 1995, 1997 Free Software Foundation, Inc.
This file is part of GNU CC.
GNU CC is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
GNU CC is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GNU CC; see the file COPYING. If not, write to
the Free Software Foundation, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA. */
/* ??? This is an old port, and is undoubtedly suffering from bit rot. */
#include <stdio.h>
#include "config.h"
#include "tree.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 "expr.h"
#include "recog.h"
/* Which cpu we're compiling for (NULL(=base), ???). */
char *arc_cpu_string;
int arc_cpu_type;
/* Name of mangle string to add to symbols to separate code compiled for each
cpu (or NULL). */
char *arc_mangle_cpu;
/* Save the operands last given to a compare for use when we
generate a scc or bcc insn. */
rtx arc_compare_op0, arc_compare_op1;
/* Name of text, data, and rodata sections, as specified on command line.
Selected by -m{text,data,rodata} flags. */
char *arc_text_string = ARC_DEFAULT_TEXT_SECTION;
char *arc_data_string = ARC_DEFAULT_DATA_SECTION;
char *arc_rodata_string = ARC_DEFAULT_RODATA_SECTION;
/* Name of text, data, and rodata sections used in varasm.c. */
char *arc_text_section;
char *arc_data_section;
char *arc_rodata_section;
/* Array of valid operand punctuation characters. */
char arc_punct_chars[256];
/* Variables used by arc_final_prescan_insn to implement conditional
execution. */
static int arc_ccfsm_state;
static int arc_ccfsm_current_cc;
static rtx arc_ccfsm_target_insn;
static int arc_ccfsm_target_label;
/* The maximum number of insns skipped which will be conditionalised if
possible. */
#define MAX_INSNS_SKIPPED 3
/* A nop is needed between a 4 byte insn that sets the condition codes and
a branch that uses them (the same isn't true for an 8 byte insn that sets
the condition codes). Set by arc_final_prescan_insn. Used by
arc_print_operand. */
static int last_insn_set_cc_p;
static int current_insn_set_cc_p;
static void record_cc_ref ();
void arc_init_reg_tables ();
/* Called by OVERRIDE_OPTIONS to initialize various things. */
void
arc_init (void)
{
if (arc_cpu_string == 0
|| !strcmp (arc_cpu_string, "base"))
{
/* Ensure we have a printable value for the .cpu pseudo-op. */
arc_cpu_string = "base";
arc_cpu_type = 0;
arc_mangle_cpu = NULL;
}
else if (ARC_EXTENSION_CPU (arc_cpu_string))
; /* nothing to do */
else
{
error ("bad value (%s) for -mcpu switch", arc_cpu_string);
arc_cpu_string = "base";
arc_cpu_type = 0;
arc_mangle_cpu = NULL;
}
/* Set the pseudo-ops for the various standard sections. */
arc_text_section = xmalloc (strlen (arc_text_string) + sizeof (ARC_SECTION_FORMAT) + 1);
sprintf (arc_text_section, ARC_SECTION_FORMAT, arc_text_string);
arc_data_section = xmalloc (strlen (arc_data_string) + sizeof (ARC_SECTION_FORMAT) + 1);
sprintf (arc_data_section, ARC_SECTION_FORMAT, arc_data_string);
arc_rodata_section = xmalloc (strlen (arc_rodata_string) + sizeof (ARC_SECTION_FORMAT) + 1);
sprintf (arc_rodata_section, ARC_SECTION_FORMAT, arc_rodata_string);
arc_init_reg_tables ();
/* Initialize array for PRINT_OPERAND_PUNCT_VALID_P. */
memset (arc_punct_chars, 0, sizeof (arc_punct_chars));
arc_punct_chars['#'] = 1;
arc_punct_chars['*'] = 1;
arc_punct_chars['?'] = 1;
arc_punct_chars['!'] = 1;
arc_punct_chars['~'] = 1;
}
/* The condition codes of the ARC, and the inverse function. */
static char *arc_condition_codes[] =
{
"al", 0, "eq", "ne", "p", "n", "c", "nc", "v", "nv",
"gt", "le", "ge", "lt", "hi", "ls", "pnz", 0
};
#define ARC_INVERSE_CONDITION_CODE(X) ((X) ^ 1)
/* Returns the index of the ARC condition code string in
`arc_condition_codes'. COMPARISON should be an rtx like
`(eq (...) (...))'. */
static int
get_arc_condition_code (comparison)
rtx comparison;
{
switch (GET_CODE (comparison))
{
case EQ : return 2;
case NE : return 3;
case GT : return 10;
case LE : return 11;
case GE : return 12;
case LT : return 13;
case GTU : return 14;
case LEU : return 15;
case LTU : return 6;
case GEU : return 7;
default : abort ();
}
/*NOTREACHED*/
return (42);
}
/* Given a comparison code (EQ, NE, etc.) and the first operand of a COMPARE,
return the mode to be used for the comparison. */
enum machine_mode
arc_select_cc_mode (op, x, y)
enum rtx_code op;
rtx x, y;
{
switch (op)
{
case EQ :
case NE :
return CCZNmode;
default :
switch (GET_CODE (x))
{
case AND :
case IOR :
case XOR :
case SIGN_EXTEND :
case ZERO_EXTEND :
return CCZNmode;
case ASHIFT :
case ASHIFTRT :
case LSHIFTRT :
return CCZNCmode;
}
}
return CCmode;
}
/* Vectors to keep interesting information about registers where it can easily
be got. We use to use the actual mode value as the bit number, but there
is (or may be) more than 32 modes now. Instead we use two tables: one
indexed by hard register number, and one indexed by mode. */
/* The purpose of arc_mode_class is to shrink the range of modes so that
they all fit (as bit numbers) in a 32 bit word (again). Each real mode is
mapped into one arc_mode_class mode. */
enum arc_mode_class {
C_MODE,
S_MODE, D_MODE, T_MODE, O_MODE,
SF_MODE, DF_MODE, TF_MODE, OF_MODE
};
/* Modes for condition codes. */
#define C_MODES (1 << (int) C_MODE)
/* Modes for single-word and smaller quantities. */
#define S_MODES ((1 << (int) S_MODE) | (1 << (int) SF_MODE))
/* Modes for double-word and smaller quantities. */
#define D_MODES (S_MODES | (1 << (int) D_MODE) | (1 << DF_MODE))
/* Modes for quad-word and smaller quantities. */
#define T_MODES (D_MODES | (1 << (int) T_MODE) | (1 << (int) TF_MODE))
/* Value is 1 if register/mode pair is acceptable on arc. */
unsigned int arc_hard_regno_mode_ok[] = {
T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, T_MODES,
T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, T_MODES,
T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, D_MODES,
D_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES,
/* ??? Leave these as S_MODES for now. */
S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES,
S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES,
S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES,
S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, C_MODES
};
unsigned int arc_mode_class [NUM_MACHINE_MODES];
enum reg_class arc_regno_reg_class[FIRST_PSEUDO_REGISTER];
void
arc_init_reg_tables ()
{
int i;
for (i = 0; i < NUM_MACHINE_MODES; i++)
{
switch (GET_MODE_CLASS (i))
{
case MODE_INT:
case MODE_PARTIAL_INT:
case MODE_COMPLEX_INT:
if (GET_MODE_SIZE (i) <= 4)
arc_mode_class[i] = 1 << (int) S_MODE;
else if (GET_MODE_SIZE (i) == 8)
arc_mode_class[i] = 1 << (int) D_MODE;
else if (GET_MODE_SIZE (i) == 16)
arc_mode_class[i] = 1 << (int) T_MODE;
else if (GET_MODE_SIZE (i) == 32)
arc_mode_class[i] = 1 << (int) O_MODE;
else
arc_mode_class[i] = 0;
break;
case MODE_FLOAT:
case MODE_COMPLEX_FLOAT:
if (GET_MODE_SIZE (i) <= 4)
arc_mode_class[i] = 1 << (int) SF_MODE;
else if (GET_MODE_SIZE (i) == 8)
arc_mode_class[i] = 1 << (int) DF_MODE;
else if (GET_MODE_SIZE (i) == 16)
arc_mode_class[i] = 1 << (int) TF_MODE;
else if (GET_MODE_SIZE (i) == 32)
arc_mode_class[i] = 1 << (int) OF_MODE;
else
arc_mode_class[i] = 0;
break;
case MODE_CC:
default:
/* mode_class hasn't been initialized yet for EXTRA_CC_MODES, so
we must explicitly check for them here. */
if (i == (int) CCmode || i == (int) CCZNmode || i == (int) CCZNCmode)
arc_mode_class[i] = 1 << (int) C_MODE;
else
arc_mode_class[i] = 0;
break;
}
}
for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
{
if (i < 60)
arc_regno_reg_class[i] = GENERAL_REGS;
else if (i == 60)
arc_regno_reg_class[i] = LPCOUNT_REG;
else if (i == 61)
arc_regno_reg_class[i] = NO_REGS /* CC_REG: must be NO_REGS */;
else
arc_regno_reg_class[i] = NO_REGS;
}
}
/* ARC specific attribute support.
The ARC has these attributes:
interrupt - for interrupt functions
*/
/* Return nonzero if IDENTIFIER is a valid decl attribute. */
int
arc_valid_machine_decl_attribute (type, attributes, identifier, args)
tree type;
tree attributes;
tree identifier;
tree args;
{
if (identifier == get_identifier ("__interrupt__")
&& list_length (args) == 1
&& TREE_CODE (TREE_VALUE (args)) == STRING_CST)
{
tree value = TREE_VALUE (args);
if (!strcmp (TREE_STRING_POINTER (value), "ilink1")
|| !strcmp (TREE_STRING_POINTER (value), "ilink2"))
return 1;
}
return 0;
}
/* Return zero if TYPE1 and TYPE are incompatible, one if they are compatible,
and two if they are nearly compatible (which causes a warning to be
generated). */
int
arc_comp_type_attributes (type1, type2)
tree type1, type2;
{
return 1;
}
/* Set the default attributes for TYPE. */
void
arc_set_default_type_attributes (type)
tree type;
{
}
/* Acceptable arguments to the call insn. */
int
call_address_operand (op, mode)
rtx op;
enum machine_mode mode;
{
return (symbolic_operand (op, mode)
|| (GET_CODE (op) == CONST_INT && LEGITIMATE_CONSTANT_P (op))
|| (GET_CODE (op) == REG));
}
int
call_operand (op, mode)
rtx op;
enum machine_mode mode;
{
if (GET_CODE (op) != MEM)
return 0;
op = XEXP (op, 0);
return call_address_operand (op, mode);
}
/* Returns 1 if OP is a symbol reference. */
int
symbolic_operand (op, mode)
rtx op;
enum machine_mode mode;
{
switch (GET_CODE (op))
{
case SYMBOL_REF:
case LABEL_REF:
case CONST :
return 1;
default:
return 0;
}
}
/* Return truth value of statement that OP is a symbolic memory
operand of mode MODE. */
int
symbolic_memory_operand (op, mode)
rtx op;
enum machine_mode mode;
{
if (GET_CODE (op) == SUBREG)
op = SUBREG_REG (op);
if (GET_CODE (op) != MEM)
return 0;
op = XEXP (op, 0);
return (GET_CODE (op) == SYMBOL_REF || GET_CODE (op) == CONST
|| GET_CODE (op) == LABEL_REF);
}
/* Return true if OP is a short immediate (shimm) value. */
int
short_immediate_operand (op, mode)
rtx op;
enum machine_mode mode;
{
if (GET_CODE (op) != CONST_INT)
return 0;
return SMALL_INT (INTVAL (op));
}
/* Return true if OP will require a long immediate (limm) value.
This is currently only used when calculating length attributes. */
int
long_immediate_operand (op, mode)
rtx op;
enum machine_mode mode;
{
switch (GET_CODE (op))
{
case SYMBOL_REF :
case LABEL_REF :
case CONST :
return 1;
case CONST_INT :
return !SMALL_INT (INTVAL (op));
case CONST_DOUBLE :
/* These can happen because large unsigned 32 bit constants are
represented this way (the multiplication patterns can cause these
to be generated). They also occur for SFmode values. */
return 1;
}
return 0;
}
/* Return true if OP is a MEM that when used as a load or store address will
require an 8 byte insn.
Load and store instructions don't allow the same possibilities but they're
similar enough that this one function will do.
This is currently only used when calculating length attributes. */
int
long_immediate_loadstore_operand (op, mode)
rtx op;
enum machine_mode mode;
{
if (GET_CODE (op) != MEM)
return 0;
op = XEXP (op, 0);
switch (GET_CODE (op))
{
case SYMBOL_REF :
case LABEL_REF :
case CONST :
return 1;
case CONST_INT :
/* This must be handled as "st c,[limm]". Ditto for load.
Technically, the assembler could translate some possibilities to
"st c,[limm/2 + limm/2]" if limm/2 will fit in a shimm, but we don't
assume that it does. */
return 1;
case CONST_DOUBLE :
/* These can happen because large unsigned 32 bit constants are
represented this way (the multiplication patterns can cause these
to be generated). They also occur for SFmode values. */
return 1;
case REG :
return 0;
case PLUS :
if (GET_CODE (XEXP (op, 1)) == CONST_INT
&& !SMALL_INT (INTVAL (XEXP (op, 1))))
return 1;
return 0;
}
return 0;
}
/* Return true if OP is an acceptable argument for a single word
move source. */
int
move_src_operand (op, mode)
rtx op;
enum machine_mode mode;
{
switch (GET_CODE (op))
{
case SYMBOL_REF :
case LABEL_REF :
case CONST :
return 1;
case CONST_INT :
return (LARGE_INT (INTVAL (op)));
case CONST_DOUBLE :
/* We can handle DImode integer constants in SImode if the value
(signed or unsigned) will fit in 32 bits. This is needed because
large unsigned 32 bit constants are represented as CONST_DOUBLEs. */
if (mode == SImode)
return arc_double_limm_p (op);
/* We can handle 32 bit floating point constants. */
if (mode == SFmode)
return GET_MODE (op) == SFmode;
return 0;
case REG :
return register_operand (op, mode);
case SUBREG :
/* (subreg (mem ...) ...) can occur here if the inner part was once a
pseudo-reg and is now a stack slot. */
if (GET_CODE (SUBREG_REG (op)) == MEM)
return address_operand (XEXP (SUBREG_REG (op), 0), mode);
else
return register_operand (op, mode);
case MEM :
return address_operand (XEXP (op, 0), mode);
default :
return 0;
}
}
/* Return true if OP is an acceptable argument for a double word
move source. */
int
move_double_src_operand (op, mode)
rtx op;
enum machine_mode mode;
{
switch (GET_CODE (op))
{
case REG :
return register_operand (op, mode);
case SUBREG :
/* (subreg (mem ...) ...) can occur here if the inner part was once a
pseudo-reg and is now a stack slot. */
if (GET_CODE (SUBREG_REG (op)) == MEM)
return move_double_src_operand (SUBREG_REG (op), mode);
else
return register_operand (op, mode);
case MEM :
/* Disallow auto inc/dec for now. */
if (GET_CODE (XEXP (op, 0)) == PRE_DEC
|| GET_CODE (XEXP (op, 0)) == PRE_INC)
return 0;
return address_operand (XEXP (op, 0), mode);
case CONST_INT :
case CONST_DOUBLE :
return 1;
default :
return 0;
}
}
/* Return true if OP is an acceptable argument for a move destination. */
int
move_dest_operand (op, mode)
rtx op;
enum machine_mode mode;
{
switch (GET_CODE (op))
{
case REG :
return register_operand (op, mode);
case SUBREG :
/* (subreg (mem ...) ...) can occur here if the inner part was once a
pseudo-reg and is now a stack slot. */
if (GET_CODE (SUBREG_REG (op)) == MEM)
return address_operand (XEXP (SUBREG_REG (op), 0), mode);
else
return register_operand (op, mode);
case MEM :
return address_operand (XEXP (op, 0), mode);
default :
return 0;
}
}
/* Return true if OP is valid load with update operand. */
int
load_update_operand (op, mode)
rtx op;
enum machine_mode mode;
{
if (GET_CODE (op) != MEM
|| GET_MODE (op) != mode)
return 0;
op = XEXP (op, 0);
if (GET_CODE (op) != PLUS
|| GET_MODE (op) != Pmode
|| !register_operand (XEXP (op, 0), Pmode)
|| !nonmemory_operand (XEXP (op, 1), Pmode))
return 0;
return 1;
}
/* Return true if OP is valid store with update operand. */
int
store_update_operand (op, mode)
rtx op;
enum machine_mode mode;
{
if (GET_CODE (op) != MEM
|| GET_MODE (op) != mode)
return 0;
op = XEXP (op, 0);
if (GET_CODE (op) != PLUS
|| GET_MODE (op) != Pmode
|| !register_operand (XEXP (op, 0), Pmode)
|| !(GET_CODE (XEXP (op, 1)) == CONST_INT
&& SMALL_INT (INTVAL (XEXP (op, 1)))))
return 0;
return 1;
}
/* Return true if OP is a non-volatile non-immediate operand.
Volatile memory refs require a special "cache-bypass" instruction
and only the standard movXX patterns are set up to handle them. */
int
nonvol_nonimm_operand (op, mode)
rtx op;
enum machine_mode mode;
{
if (GET_CODE (op) == MEM && MEM_VOLATILE_P (op))
return 0;
return nonimmediate_operand (op, mode);
}
/* Accept integer operands in the range -0x80000000..0x7fffffff. We have
to check the range carefully since this predicate is used in DImode
contexts. */
int
const_sint32_operand (op, mode)
rtx op;
enum machine_mode mode;
{
/* All allowed constants will fit a CONST_INT. */
return (GET_CODE (op) == CONST_INT
&& (INTVAL (op) >= (-0x7fffffff - 1) && INTVAL (op) <= 0x7fffffff));
}
/* Accept integer operands in the range 0..0xffffffff. We have to check the
range carefully since this predicate is used in DImode contexts. Also, we
need some extra crud to make it work when hosted on 64-bit machines. */
int
const_uint32_operand (op, mode)
rtx op;
enum machine_mode mode;
{
#if HOST_BITS_PER_WIDE_INT > 32
/* All allowed constants will fit a CONST_INT. */
return (GET_CODE (op) == CONST_INT
&& (INTVAL (op) >= 0 && INTVAL (op) <= 0xffffffffL));
#else
return ((GET_CODE (op) == CONST_INT && INTVAL (op) >= 0)
|| (GET_CODE (op) == CONST_DOUBLE && CONST_DOUBLE_HIGH (op) == 0));
#endif
}
/* Return 1 if OP is a comparison operator valid for the mode of CC.
This allows the use of MATCH_OPERATOR to recognize all the branch insns.
Some insns only set a few bits in the condition code. So only allow those
comparisons that use the bits that are valid. */
int
proper_comparison_operator (op, mode)
rtx op;
enum machine_mode mode;
{
enum rtx_code code = GET_CODE (op);
if (GET_RTX_CLASS (code) != '<')
return 0;
if (GET_MODE (XEXP (op, 0)) == CCZNmode)
return (code == EQ || code == NE);
if (GET_MODE (XEXP (op, 0)) == CCZNCmode)
return (code == EQ || code == NE
|| code == LTU || code == GEU || code == GTU || code == LEU);
return 1;
}
/* Misc. utilities. */
/* X and Y are two things to compare using CODE. Emit the compare insn and
return the rtx for the cc reg in the proper mode. */
rtx
gen_compare_reg (code, x, y)
enum rtx_code code;
rtx x, y;
{
enum machine_mode mode = SELECT_CC_MODE (code, x, y);
rtx cc_reg;
cc_reg = gen_rtx (REG, mode, 61);
emit_insn (gen_rtx (SET, VOIDmode, cc_reg,
gen_rtx (COMPARE, mode, x, y)));
return cc_reg;
}
/* Return 1 if VALUE, a const_double, will fit in a limm (4 byte number).
We assume the value can be either signed or unsigned. */
int
arc_double_limm_p (value)
rtx value;
{
HOST_WIDE_INT low, high;
if (GET_CODE (value) != CONST_DOUBLE)
abort ();
low = CONST_DOUBLE_LOW (value);
high = CONST_DOUBLE_HIGH (value);
if (low & 0x80000000)
{
return (((unsigned HOST_WIDE_INT) low <= 0xffffffff && high == 0)
|| (((low & - (unsigned HOST_WIDE_INT) 0x80000000)
== - (unsigned HOST_WIDE_INT) 0x80000000)
&& high == -1));
}
else
{
return (unsigned HOST_WIDE_INT) low <= 0x7fffffff && high == 0;
}
}
/* Do any needed setup for a variadic function. For the ARC, we must
create a register parameter block, and then copy any anonymous arguments
in registers to memory.
CUM has not been updated for the last named argument which has type TYPE
and mode MODE, and we rely on this fact.
We do things a little weird here. We're supposed to only allocate space
for the anonymous arguments. However we need to keep the stack eight byte
aligned. So we round the space up if necessary, and leave it to va-arc.h
to compensate. */
void
arc_setup_incoming_varargs (cum, mode, type, pretend_size, no_rtl)
CUMULATIVE_ARGS *cum;
enum machine_mode mode;
tree type;
int *pretend_size;
int no_rtl;
{
int first_anon_arg;
/* All BLKmode values are passed by reference. */
if (mode == BLKmode)
abort ();
/* We must treat `__builtin_va_alist' as an anonymous arg. */
if (current_function_varargs)
first_anon_arg = *cum;
else
first_anon_arg = *cum + ((GET_MODE_SIZE (mode) + UNITS_PER_WORD - 1)
/ UNITS_PER_WORD);
if (first_anon_arg < MAX_ARC_PARM_REGS && !no_rtl)
{
/* Note that first_reg_offset < MAX_ARC_PARM_REGS. */
int first_reg_offset = first_anon_arg;
/* Size in words to "pretend" allocate. */
int size = MAX_ARC_PARM_REGS - first_reg_offset;
/* Extra slop to keep stack eight byte aligned. */
int align_slop = size & 1;
rtx regblock;
regblock = gen_rtx (MEM, BLKmode,
plus_constant (arg_pointer_rtx,
FIRST_PARM_OFFSET (0)
+ align_slop * UNITS_PER_WORD));
move_block_from_reg (first_reg_offset, regblock,
MAX_ARC_PARM_REGS - first_reg_offset,
((MAX_ARC_PARM_REGS - first_reg_offset)
* UNITS_PER_WORD));
*pretend_size = ((MAX_ARC_PARM_REGS - first_reg_offset + align_slop)
* UNITS_PER_WORD);
}
}
/* Cost functions. */
/* Provide the costs of an addressing mode that contains ADDR.
If ADDR is not a valid address, its cost is irrelevant. */
int
arc_address_cost (addr)
rtx addr;
{
switch (GET_CODE (addr))
{
case REG :
/* This is handled in the macro that calls us.
It's here for documentation. */
return 1;
case LABEL_REF :
case SYMBOL_REF :
case CONST :
return 2;
case PLUS :
{
register rtx plus0 = XEXP (addr, 0);
register rtx plus1 = XEXP (addr, 1);
if (GET_CODE (plus0) != REG)
break;
switch (GET_CODE (plus1))
{
case CONST_INT :
return SMALL_INT (plus1) ? 1 : 2;
case CONST :
case SYMBOL_REF :
case LABEL_REF :
return 2;
default:
break;
}
break;
}
}
return 4;
}
/* Function prologue/epilogue handlers. */
/* ARC stack frames look like:
Before call After call
+-----------------------+ +-----------------------+
| | | |
high | local variables, | | local variables, |
mem | reg save area, etc. | | reg save area, etc. |
| | | |
+-----------------------+ +-----------------------+
| | | |
| arguments on stack. | | arguments on stack. |
| | | |
SP+16->+-----------------------+FP+48->+-----------------------+
| 4 word save area for | | reg parm save area, |
| return addr, prev %fp | | only created for |
SP+0->+-----------------------+ | variable argument |
| functions |
FP+16->+-----------------------+
| 4 word save area for |
| return addr, prev %fp |
FP+0->+-----------------------+
| |
| local variables |
| |
+-----------------------+
| |
| register save area |
| |
+-----------------------+
| |
| alloca allocations |
| |
+-----------------------+
| |
| arguments on stack |
| |
SP+16->+-----------------------+
low | 4 word save area for |
memory | return addr, prev %fp |
SP+0->+-----------------------+
Notes:
1) The "reg parm save area" does not exist for non variable argument fns.
The "reg parm save area" can be eliminated completely if we created our
own va-arc.h, but that has tradeoffs as well (so it's not done). */
/* Structure to be filled in by arc_compute_frame_size with register
save masks, and offsets for the current function. */
struct arc_frame_info
{
unsigned int total_size; /* # bytes that the entire frame takes up. */
unsigned int extra_size; /* # bytes of extra stuff. */
unsigned int pretend_size; /* # bytes we push and pretend caller did. */
unsigned int args_size; /* # bytes that outgoing arguments take up. */
unsigned int reg_size; /* # bytes needed to store regs. */
unsigned int var_size; /* # bytes that variables take up. */
unsigned int reg_offset; /* Offset from new sp to store regs. */
unsigned int gmask; /* Mask of saved gp registers. */
int initialized; /* Nonzero if frame size already calculated. */
};
/* Current frame information calculated by arc_compute_frame_size. */
static struct arc_frame_info current_frame_info;
/* Zero structure to initialize current_frame_info. */
static struct arc_frame_info zero_frame_info;
/* Type of function DECL.
The result is cached. To reset the cache at the end of a function,
call with DECL = NULL_TREE. */
enum arc_function_type
arc_compute_function_type (decl)
tree decl;
{
tree a;
/* Cached value. */
static enum arc_function_type fn_type = ARC_FUNCTION_UNKNOWN;
/* Last function we were called for. */
static tree last_fn = NULL_TREE;
/* Resetting the cached value? */
if (decl == NULL_TREE)
{
fn_type = ARC_FUNCTION_UNKNOWN;
last_fn = NULL_TREE;
return fn_type;
}
if (decl == last_fn && fn_type != ARC_FUNCTION_UNKNOWN)
return fn_type;
/* Assume we have a normal function (not an interrupt handler). */
fn_type = ARC_FUNCTION_NORMAL;
/* Now see if this is an interrupt handler. */
for (a = DECL_MACHINE_ATTRIBUTES (current_function_decl);
a;
a = TREE_CHAIN (a))
{
tree name = TREE_PURPOSE (a), args = TREE_VALUE (a);
if (name == get_identifier ("__interrupt__")
&& list_length (args) == 1
&& TREE_CODE (TREE_VALUE (args)) == STRING_CST)
{
tree value = TREE_VALUE (args);
if (!strcmp (TREE_STRING_POINTER (value), "ilink1"))
fn_type = ARC_FUNCTION_ILINK1;
else if (!strcmp (TREE_STRING_POINTER (value), "ilink2"))
fn_type = ARC_FUNCTION_ILINK2;
else
abort ();
break;
}
}
last_fn = decl;
return fn_type;
}
#define ILINK1_REGNUM 29
#define ILINK2_REGNUM 30
#define RETURN_ADDR_REGNUM 31
#define FRAME_POINTER_MASK (1 << (FRAME_POINTER_REGNUM))
#define RETURN_ADDR_MASK (1 << (RETURN_ADDR_REGNUM))
/* Tell prologue and epilogue if register REGNO should be saved / restored.
The return address and frame pointer are treated separately.
Don't consider them here. */
#define MUST_SAVE_REGISTER(regno, interrupt_p) \
((regno) != RETURN_ADDR_REGNUM && (regno) != FRAME_POINTER_REGNUM \
&& (regs_ever_live[regno] && (!call_used_regs[regno] || interrupt_p)))
#define MUST_SAVE_RETURN_ADDR (regs_ever_live[RETURN_ADDR_REGNUM])
/* Return the bytes needed to compute the frame pointer from the current
stack pointer.
SIZE is the size needed for local variables. */
unsigned int
arc_compute_frame_size (size)
int size; /* # of var. bytes allocated. */
{
int regno;
unsigned int total_size, var_size, args_size, pretend_size, extra_size;
unsigned int reg_size, reg_offset;
unsigned int gmask;
enum arc_function_type fn_type;
int interrupt_p;
var_size = size;
args_size = current_function_outgoing_args_size;
pretend_size = current_function_pretend_args_size;
extra_size = FIRST_PARM_OFFSET (0);
total_size = extra_size + pretend_size + args_size + var_size;
reg_offset = FIRST_PARM_OFFSET(0) + current_function_outgoing_args_size;
reg_size = 0;
gmask = 0;
/* See if this is an interrupt handler. Call used registers must be saved
for them too. */
fn_type = arc_compute_function_type (current_function_decl);
interrupt_p = ARC_INTERRUPT_P (fn_type);
/* Calculate space needed for registers.
??? We ignore the extension registers for now. */
for (regno = 0; regno <= 31; regno++)
{
if (MUST_SAVE_REGISTER (regno, interrupt_p))
{
reg_size += UNITS_PER_WORD;
gmask |= 1 << regno;
}
}
total_size += reg_size;
/* If the only space to allocate is the fp/blink save area this is an
empty frame. However, if we'll be making a function call we need to
allocate a stack frame for our callee's fp/blink save area. */
if (total_size == extra_size
&& !MUST_SAVE_RETURN_ADDR)
total_size = extra_size = 0;
total_size = ARC_STACK_ALIGN (total_size);
/* Save computed information. */
current_frame_info.total_size = total_size;
current_frame_info.extra_size = extra_size;
current_frame_info.pretend_size = pretend_size;
current_frame_info.var_size = var_size;
current_frame_info.args_size = args_size;
current_frame_info.reg_size = reg_size;
current_frame_info.reg_offset = reg_offset;
current_frame_info.gmask = gmask;
current_frame_info.initialized = reload_completed;
/* Ok, we're done. */
return total_size;
}
/* Common code to save/restore registers. */
void
arc_save_restore (file, base_reg, offset, gmask, op)
FILE *file;
char *base_reg;
unsigned int offset;
unsigned int gmask;
char *op;
{
int regno;
if (gmask == 0)
return;
for (regno = 0; regno <= 31; regno++)
{
if ((gmask & (1L << regno)) != 0)
{
fprintf (file, "\t%s %s,[%s,%d]\n",
op, reg_names[regno], base_reg, offset);
offset += UNITS_PER_WORD;
}
}
}
/* Set up the stack and frame pointer (if desired) for the function. */
void
arc_output_function_prologue (file, size)
FILE *file;
int size;
{
char *sp_str = reg_names[STACK_POINTER_REGNUM];
char *fp_str = reg_names[FRAME_POINTER_REGNUM];
unsigned int gmask = current_frame_info.gmask;
enum arc_function_type fn_type = arc_compute_function_type (current_function_decl);
/* If this is an interrupt handler, set up our stack frame.
??? Optimize later. */
if (ARC_INTERRUPT_P (fn_type))
{
fprintf (file, "\t%s interrupt handler\n",
ASM_COMMENT_START);
fprintf (file, "\tsub %s,%s,16\n", sp_str, sp_str);
}
/* This is only for the human reader. */
fprintf (file, "\t%s BEGIN PROLOGUE %s vars= %d, regs= %d, args= %d, extra= %d\n",
ASM_COMMENT_START, ASM_COMMENT_START,
current_frame_info.var_size,
current_frame_info.reg_size / 4,
current_frame_info.args_size,
current_frame_info.extra_size);
size = ARC_STACK_ALIGN (size);
size = (! current_frame_info.initialized
? arc_compute_frame_size (size)
: current_frame_info.total_size);
/* These cases shouldn't happen. Catch them now. */
if (size == 0 && gmask)
abort ();
/* Allocate space for register arguments if this is a variadic function. */
if (current_frame_info.pretend_size != 0)
fprintf (file, "\tsub %s,%s,%d\n",
sp_str, sp_str, current_frame_info.pretend_size);
/* The home-grown ABI says link register is saved first. */
if (MUST_SAVE_RETURN_ADDR)
fprintf (file, "\tst %s,[%s,%d]\n",
reg_names[RETURN_ADDR_REGNUM], sp_str, UNITS_PER_WORD);
/* Set up the previous frame pointer next (if we need to). */
if (frame_pointer_needed)
{
fprintf (file, "\tst %s,[%s]\n", fp_str, sp_str);
fprintf (file, "\tmov %s,%s\n", fp_str, sp_str);
}
/* ??? We don't handle the case where the saved regs are more than 252
bytes away from sp. This can be handled by decrementing sp once, saving
the regs, and then decrementing it again. The epilogue doesn't have this
problem as the `ld' insn takes reg+limm values (though it would be more
efficient to avoid reg+limm). */
/* Allocate the stack frame. */
if (size - current_frame_info.pretend_size > 0)
fprintf (file, "\tsub %s,%s,%d\n",
sp_str, sp_str, size - current_frame_info.pretend_size);
/* Save any needed call-saved regs (and call-used if this is an
interrupt handler). */
arc_save_restore (file, sp_str, current_frame_info.reg_offset,
/* The zeroing of these two bits is unnecessary,
but leave this in for clarity. */
gmask & ~(FRAME_POINTER_MASK | RETURN_ADDR_MASK),
"st");
fprintf (file, "\t%s END PROLOGUE\n", ASM_COMMENT_START);
}
/* Do any necessary cleanup after a function to restore stack, frame,
and regs. */
void
arc_output_function_epilogue (file, size)
FILE *file;
int size;
{
rtx epilogue_delay = current_function_epilogue_delay_list;
int noepilogue = FALSE;
enum arc_function_type fn_type = arc_compute_function_type (current_function_decl);
/* This is only for the human reader. */
fprintf (file, "\t%s EPILOGUE\n", ASM_COMMENT_START);
size = ARC_STACK_ALIGN (size);
size = (!current_frame_info.initialized
? arc_compute_frame_size (size)
: current_frame_info.total_size);
if (size == 0 && epilogue_delay == 0)
{
rtx insn = get_last_insn ();
/* If the last insn was a BARRIER, we don't have to write any code
because a jump (aka return) was put there. */
if (GET_CODE (insn) == NOTE)
insn = prev_nonnote_insn (insn);
if (insn && GET_CODE (insn) == BARRIER)
noepilogue = TRUE;
}
if (!noepilogue)
{
unsigned int pretend_size = current_frame_info.pretend_size;
unsigned int frame_size = size - pretend_size;
int restored, fp_restored_p;
int can_trust_sp_p = !current_function_calls_alloca;
char *sp_str = reg_names[STACK_POINTER_REGNUM];
char *fp_str = reg_names[FRAME_POINTER_REGNUM];
/* ??? There are lots of optimizations that can be done here.
EG: Use fp to restore regs if it's closer.
Maybe in time we'll do them all. For now, always restore regs from
sp, but don't restore sp if we don't have to. */
if (!can_trust_sp_p)
{
if (!frame_pointer_needed)
abort ();
fprintf (file,"\tsub %s,%s,%d\t\t%s sp not trusted here\n",
sp_str, fp_str, frame_size, ASM_COMMENT_START);
}
/* Restore any saved registers. */
arc_save_restore (file, sp_str, current_frame_info.reg_offset,
/* The zeroing of these two bits is unnecessary,
but leave this in for clarity. */
current_frame_info.gmask & ~(FRAME_POINTER_MASK | RETURN_ADDR_MASK),
"ld");
if (MUST_SAVE_RETURN_ADDR)
fprintf (file, "\tld %s,[%s,%d]\n",
reg_names[RETURN_ADDR_REGNUM],
frame_pointer_needed ? fp_str : sp_str,
UNITS_PER_WORD + (frame_pointer_needed ? 0 : frame_size));
/* Keep track of how much of the stack pointer we've restored.
It makes the following a lot more readable. */
restored = 0;
fp_restored_p = 0;
/* We try to emit the epilogue delay slot insn right after the load
of the return address register so that it can execute with the
stack intact. Secondly, loads are delayed. */
/* ??? If stack intactness is important, always emit now. */
if (MUST_SAVE_RETURN_ADDR && epilogue_delay != NULL_RTX)
{
final_scan_insn (XEXP (epilogue_delay, 0), file, 1, -2, 1);
epilogue_delay = NULL_RTX;
}
if (frame_pointer_needed)
{
/* Try to restore the frame pointer in the delay slot. We can't,
however, if any of these is true. */
if (epilogue_delay != NULL_RTX
|| !SMALL_INT (frame_size)
|| pretend_size
|| ARC_INTERRUPT_P (fn_type))
{
/* Note that we restore fp and sp here! */
fprintf (file, "\tld.a %s,[%s,%d]\n", fp_str, sp_str, frame_size);
restored += frame_size;
fp_restored_p = 1;
}
}
else if (!SMALL_INT (size /* frame_size + pretend_size */)
|| ARC_INTERRUPT_P (fn_type))
{
fprintf (file, "\tadd %s,%s,%d\n", sp_str, sp_str, frame_size);
restored += frame_size;
}
/* These must be done before the return insn because the delay slot
does the final stack restore. */
if (ARC_INTERRUPT_P (fn_type))
{
if (epilogue_delay)
{
final_scan_insn (XEXP (epilogue_delay, 0), file, 1, -2, 1);
}
}
/* Emit the return instruction. */
{
static int regs[4] = {
0, RETURN_ADDR_REGNUM, ILINK1_REGNUM, ILINK2_REGNUM
};
fprintf (file, "\tj.d %s\n", reg_names[regs[fn_type]]);
}
/* If the only register saved is the return address, we need a
nop, unless we have an instruction to put into it. Otherwise
we don't since reloading multiple registers doesn't reference
the register being loaded. */
if (ARC_INTERRUPT_P (fn_type))
fprintf (file, "\tadd %s,%s,16\n", sp_str, sp_str);
else if (epilogue_delay != NULL_RTX)
{
if (frame_pointer_needed && !fp_restored_p)
abort ();
if (restored < size)
abort ();
final_scan_insn (XEXP (epilogue_delay, 0), file, 1, -2, 1);
}
else if (frame_pointer_needed && !fp_restored_p)
{
if (!SMALL_INT (frame_size))
abort ();
/* Note that we restore fp and sp here! */
fprintf (file, "\tld.a %s,[%s,%d]\n", fp_str, sp_str, frame_size);
}
else if (restored < size)
{
if (!SMALL_INT (size - restored))
abort ();
fprintf (file, "\tadd %s,%s,%d\n",
sp_str, sp_str, size - restored);
}
else
fprintf (file, "\tnop\n");
}
/* Reset state info for each function. */
current_frame_info = zero_frame_info;
arc_compute_function_type (NULL_TREE);
}
/* Define the number of delay slots needed for the function epilogue.
Interrupt handlers can't have any epilogue delay slots (it's always needed
for something else, I think). For normal functions, we have to worry about
using call-saved regs as they'll be restored before the delay slot insn.
Functions with non-empty frames already have enough choices for the epilogue
delay slot so for now we only consider functions with empty frames. */
int
arc_delay_slots_for_epilogue ()
{
if (arc_compute_function_type (current_function_decl) != ARC_FUNCTION_NORMAL)
return 0;
if (!current_frame_info.initialized)
(void) arc_compute_frame_size (get_frame_size ());
if (current_frame_info.total_size == 0)
return 1;
return 0;
}
/* Return true if TRIAL is a valid insn for the epilogue delay slot.
Any single length instruction which doesn't reference the stack or frame
pointer or any call-saved register is OK. SLOT will always be 0. */
int
arc_eligible_for_epilogue_delay (trial, slot)
rtx trial;
int slot;
{
if (slot != 0)
abort ();
if (get_attr_length (trial) == 1
/* If registers where saved, presumably there's more than enough
possibilities for the delay slot. The alternative is something
more complicated (of course, if we expanded the epilogue as rtl
this problem would go away). */
/* ??? Note that this will always be true since only functions with
empty frames have epilogue delay slots. See
arc_delay_slots_for_epilogue. */
&& current_frame_info.gmask == 0
&& ! reg_mentioned_p (stack_pointer_rtx, PATTERN (trial))
&& ! reg_mentioned_p (frame_pointer_rtx, PATTERN (trial)))
return 1;
return 0;
}
/* PIC */
/* Set up PIC-specific rtl. This should not cause any insns
to be emitted. */
void
arc_initialize_pic ()
{
/* nothing to do */
}
/* Emit special PIC prologues and epilogues. */
void
arc_finalize_pic ()
{
/* nothing to do */
}
/* Return true if OP is a shift operator. */
int
shift_operator (op, mode)
rtx op;
enum machine_mode mode;
{
switch (GET_CODE (op))
{
case ASHIFTRT:
case LSHIFTRT:
case ASHIFT:
return 1;
default:
return 0;
}
}
/* Output the assembler code for doing a shift.
We go to a bit of trouble to generate efficient code as the ARC only has
single bit shifts. This is taken from the h8300 port. We only have one
mode of shifting and can't access individual bytes like the h8300 can, so
this is greatly simplified (at the expense of not generating hyper-
efficient code).
This function is not used if the variable shift insns are present. */
/* ??? We assume the output operand is the same as operand 1.
This can be optimized (deleted) in the case of 1 bit shifts. */
/* ??? We use the loop register here. We don't use it elsewhere (yet) and
using it here will give us a chance to play with it. */
char *
output_shift (operands)
rtx *operands;
{
static int loopend_lab;
rtx shift = operands[3];
enum machine_mode mode = GET_MODE (shift);
enum rtx_code code = GET_CODE (shift);
char *shift_one;
if (mode != SImode)
abort ();
switch (code)
{
case ASHIFT: shift_one = "asl %0,%0"; break;
case ASHIFTRT: shift_one = "asr %0,%0"; break;
case LSHIFTRT: shift_one = "lsr %0,%0"; break;
default: abort ();
}
if (GET_CODE (operands[2]) != CONST_INT)
{
if (optimize)
output_asm_insn ("mov lp_count,%2", operands);
else
output_asm_insn ("mov %4,%2", operands);
goto shiftloop;
}
else
{
int n = INTVAL (operands[2]);
/* If the count is negative, make it 0. */
if (n < 0)
n = 0;
/* If the count is too big, truncate it.
ANSI says shifts of GET_MODE_BITSIZE are undefined - we choose to
do the intuitive thing. */
else if (n > GET_MODE_BITSIZE (mode))
n = GET_MODE_BITSIZE (mode);
/* First see if we can do them inline. */
if (n <= 8)
{
while (--n >= 0)
output_asm_insn (shift_one, operands);
}
/* See if we can use a rotate/and. */
else if (n == BITS_PER_WORD - 1)
{
switch (code)
{
case ASHIFT :
output_asm_insn ("and %0,%0,1\n\tror %0,%0", operands);
break;
case ASHIFTRT :
/* The ARC doesn't have a rol insn. Use something else. */
output_asm_insn ("asl.f 0,%0\n\tsbc %0,0,0", operands);
break;
case LSHIFTRT :
/* The ARC doesn't have a rol insn. Use something else. */
output_asm_insn ("asl.f 0,%0\n\tadc %0,0,0", operands);
break;
}
}
/* Must loop. */
else
{
char buf[100];
if (optimize)
output_asm_insn ("mov lp_count,%c2", operands);
else
output_asm_insn ("mov %4,%c2", operands);
shiftloop:
if (optimize)
{
if (flag_pic)
sprintf ("lr %%4,[status]\n\tadd %%4,%%4,6\t%s single insn loop start",
ASM_COMMENT_START);
else
sprintf (buf, "mov %%4,%%%%st(1f)\t%s (single insn loop start) >> 2",
ASM_COMMENT_START);
output_asm_insn (buf, operands);
output_asm_insn ("sr %4,[lp_start]", operands);
output_asm_insn ("add %4,%4,1", operands);
output_asm_insn ("sr %4,[lp_end]", operands);
output_asm_insn ("nop\n\tnop", operands);
if (flag_pic)
asm_fprintf (asm_out_file, "\t%s single insn loop\n",
ASM_COMMENT_START);
else
asm_fprintf (asm_out_file, "1:\t%s single insn loop\n",
ASM_COMMENT_START);
output_asm_insn (shift_one, operands);
}
else
{
asm_fprintf (asm_out_file, "1:\t%s begin shift loop\n",
ASM_COMMENT_START);
output_asm_insn ("sub.f %4,%4,1", operands);
output_asm_insn ("nop", operands);
output_asm_insn ("bn.nd 2f", operands);
output_asm_insn (shift_one, operands);
output_asm_insn ("b.nd 1b", operands);
asm_fprintf (asm_out_file, "2:\t%s end shift loop\n",
ASM_COMMENT_START);
}
}
}
return "";
}
/* Nested function support. */
/* Emit RTL insns to initialize the variable parts of a trampoline.
FNADDR is an RTX for the address of the function's pure code.
CXT is an RTX for the static chain value for the function. */
void
arc_initialize_trampoline (tramp, fnaddr, cxt)
rtx tramp, fnaddr, cxt;
{
}
/* Set the cpu type and print out other fancy things,
at the top of the file. */
void
arc_asm_file_start (file)
FILE *file;
{
fprintf (file, "\t.cpu %s\n", arc_cpu_string);
}
/* Print operand X (an rtx) in assembler syntax to file FILE.
CODE is a letter or dot (`z' in `%z0') or 0 if no letter was specified.
For `%' followed by punctuation, CODE is the punctuation and X is null. */
void
arc_print_operand (file, x, code)
FILE *file;
rtx x;
int code;
{
switch (code)
{
case '#' :
/* Conditional branches. For now these are equivalent. */
case '*' :
/* Unconditional branches. Output the appropriate delay slot suffix. */
if (!final_sequence || XVECLEN (final_sequence, 0) == 1)
{
/* There's nothing in the delay slot. */
fputs (".nd", file);
}
else
{
rtx jump = XVECEXP (final_sequence, 0, 0);
rtx delay = XVECEXP (final_sequence, 0, 1);
if (INSN_ANNULLED_BRANCH_P (jump))
fputs (INSN_FROM_TARGET_P (delay) ? ".jd" : ".nd", file);
else
fputs (".d", file);
}
return;
case '?' : /* with leading "." */
case '!' : /* without leading "." */
/* This insn can be conditionally executed. See if the ccfsm machinery
says it should be conditionalized. */
if (arc_ccfsm_state == 3 || arc_ccfsm_state == 4)
{
/* Is this insn in a delay slot? */
if (final_sequence && XVECLEN (final_sequence, 0) == 2)
{
rtx insn = XVECEXP (final_sequence, 0, 1);
/* If the insn is annulled and is from the target path, we need
to inverse the condition test. */
if (INSN_ANNULLED_BRANCH_P (insn))
{
if (INSN_FROM_TARGET_P (insn))
fprintf (file, "%s%s",
code == '?' ? "." : "",
arc_condition_codes[ARC_INVERSE_CONDITION_CODE (arc_ccfsm_current_cc)]);
else
fprintf (file, "%s%s",
code == '?' ? "." : "",
arc_condition_codes[arc_ccfsm_current_cc]);
}
else
/* This insn is executed for either path, so don't
conditionalize it at all. */
; /* nothing to do */
}
else
{
/* This insn isn't in a delay slot. */
fprintf (file, "%s%s",
code == '?' ? "." : "",
arc_condition_codes[arc_ccfsm_current_cc]);
}
}
return;
case '~' :
/* Output a nop if we're between a set of the condition codes,
and a conditional branch. */
if (last_insn_set_cc_p)
fputs ("nop\n\t", file);
return;
case 'd' :
fputs (arc_condition_codes[get_arc_condition_code (x)], file);
return;
case 'D' :
fputs (arc_condition_codes[ARC_INVERSE_CONDITION_CODE
(get_arc_condition_code (x))],
file);
return;
case 'R' :
/* Write second word of DImode or DFmode reference,
register or memory. */
if (GET_CODE (x) == REG)
fputs (reg_names[REGNO (x)+1], file);
else if (GET_CODE (x) == MEM)
{
fputc ('[', file);
/* Handle possible auto-increment. Since it is pre-increment and
we have already done it, we can just use an offset of four. */
/* ??? This is taken from rs6000.c I think. I don't think it is
currently necessary, but keep it around. */
if (GET_CODE (XEXP (x, 0)) == PRE_INC
|| GET_CODE (XEXP (x, 0)) == PRE_DEC)
output_address (plus_constant (XEXP (XEXP (x, 0), 0), 4));
else
output_address (plus_constant (XEXP (x, 0), 4));
fputc (']', file);
}
else
output_operand_lossage ("invalid operand to %R code");
return;
case 'S' :
if ((GET_CODE (x) == SYMBOL_REF && SYMBOL_REF_FLAG (x))
|| GET_CODE (x) == LABEL_REF)
{
fprintf (file, "%%st(");
output_addr_const (file, x);
fprintf (file, ")");
return;
}
break;
case 'H' :
case 'L' :
if (GET_CODE (x) == REG)
{
/* L = least significant word, H = most significant word */
if ((TARGET_BIG_ENDIAN != 0) ^ (code == 'L'))
fputs (reg_names[REGNO (x)], file);
else
fputs (reg_names[REGNO (x)+1], file);
}
else if (GET_CODE (x) == CONST_INT
|| GET_CODE (x) == CONST_DOUBLE)
{
rtx first, second;
split_double (x, &first, &second);
fprintf (file, "0x%08lx",
code == 'L' ? INTVAL (first) : INTVAL (second));
}
else
output_operand_lossage ("invalid operand to %H/%L code");
return;
case 'A' :
{
REAL_VALUE_TYPE d;
char str[30];
if (GET_CODE (x) != CONST_DOUBLE
|| GET_MODE_CLASS (GET_MODE (x)) != MODE_FLOAT)
abort ();
REAL_VALUE_FROM_CONST_DOUBLE (d, x);
REAL_VALUE_TO_DECIMAL (d, "%.20e", str);
fprintf (file, "%s", str);
return;
}
case 'U' :
/* Output a load/store with update indicator if appropriate. */
if (GET_CODE (x) == MEM)
{
if (GET_CODE (XEXP (x, 0)) == PRE_INC
|| GET_CODE (XEXP (x, 0)) == PRE_DEC)
fputs (".a", file);
}
else
output_operand_lossage ("invalid operand to %U code");
return;
case 'V' :
/* Output cache bypass indicator for a load/store insn. Volatile memory
refs are defined to use the cache bypass mechanism. */
if (GET_CODE (x) == MEM)
{
if (MEM_VOLATILE_P (x))
fputs (".di", file);
}
else
output_operand_lossage ("invalid operand to %V code");
return;
case 0 :
/* Do nothing special. */
break;
default :
/* Unknown flag. */
output_operand_lossage ("invalid operand output code");
}
switch (GET_CODE (x))
{
case REG :
fputs (reg_names[REGNO (x)], file);
break;
case MEM :
fputc ('[', file);
if (GET_CODE (XEXP (x, 0)) == PRE_INC)
output_address (plus_constant (XEXP (XEXP (x, 0), 0),
GET_MODE_SIZE (GET_MODE (x))));
else if (GET_CODE (XEXP (x, 0)) == PRE_DEC)
output_address (plus_constant (XEXP (XEXP (x, 0), 0),
- GET_MODE_SIZE (GET_MODE (x))));
else
output_address (XEXP (x, 0));
fputc (']', file);
break;
case CONST_DOUBLE :
/* We handle SFmode constants here as output_addr_const doesn't. */
if (GET_MODE (x) == SFmode)
{
REAL_VALUE_TYPE d;
long l;
REAL_VALUE_FROM_CONST_DOUBLE (d, x);
REAL_VALUE_TO_TARGET_SINGLE (d, l);
fprintf (file, "0x%08lx", l);
break;
}
/* Fall through. Let output_addr_const deal with it. */
default :
output_addr_const (file, x);
break;
}
}
/* Print a memory address as an operand to reference that memory location. */
void
arc_print_operand_address (file, addr)
FILE *file;
rtx addr;
{
register rtx base, index = 0;
int offset = 0;
switch (GET_CODE (addr))
{
case REG :
fputs (reg_names[REGNO (addr)], file);
break;
case SYMBOL_REF :
if (/*???*/ 0 && SYMBOL_REF_FLAG (addr))
{
fprintf (file, "%%st(");
output_addr_const (file, addr);
fprintf (file, ")");
}
else
output_addr_const (file, addr);
break;
case PLUS :
if (GET_CODE (XEXP (addr, 0)) == CONST_INT)
offset = INTVAL (XEXP (addr, 0)), base = XEXP (addr, 1);
else if (GET_CODE (XEXP (addr, 1)) == CONST_INT)
offset = INTVAL (XEXP (addr, 1)), base = XEXP (addr, 0);
else
base = XEXP (addr, 0), index = XEXP (addr, 1);
if (GET_CODE (base) != REG)
abort ();
fputs (reg_names[REGNO (base)], file);
if (index == 0)
{
if (offset != 0)
fprintf (file, ",%d", offset);
}
else if (GET_CODE (index) == REG)
fprintf (file, ",%s", reg_names[REGNO (index)]);
else if (GET_CODE (index) == SYMBOL_REF)
fputc (',', file), output_addr_const (file, index);
else
abort ();
break;
case PRE_INC :
case PRE_DEC :
/* We shouldn't get here as we've lost the mode of the memory object
(which says how much to inc/dec by. */
abort ();
break;
default :
output_addr_const (file, addr);
break;
}
}
/* Update compare/branch separation marker. */
static void
record_cc_ref (insn)
rtx insn;
{
last_insn_set_cc_p = current_insn_set_cc_p;
switch (get_attr_cond (insn))
{
case COND_SET :
case COND_SET_ZN :
case COND_SET_ZNC :
if (get_attr_length (insn) == 1)
current_insn_set_cc_p = 1;
else
current_insn_set_cc_p = 0;
break;
default :
current_insn_set_cc_p = 0;
break;
}
}
/* Conditional execution support.
This is based on the ARM port but for now is much simpler.
A finite state machine takes care of noticing whether or not instructions
can be conditionally executed, and thus decrease execution time and code
size by deleting branch instructions. The fsm is controlled by
final_prescan_insn, and controls the actions of PRINT_OPERAND. The patterns
in the .md file for the branch insns also have a hand in this. */
/* The state of the fsm controlling condition codes are:
0: normal, do nothing special
1: don't output this insn
2: don't output this insn
3: make insns conditional
4: make insns conditional
State transitions (state->state by whom, under what condition):
0 -> 1 final_prescan_insn, if insn is conditional branch
0 -> 2 final_prescan_insn, if the `target' is an unconditional branch
1 -> 3 branch patterns, after having not output the conditional branch
2 -> 4 branch patterns, after having not output the conditional branch
3 -> 0 ASM_OUTPUT_INTERNAL_LABEL, if the `target' label is reached
(the target label has CODE_LABEL_NUMBER equal to
arc_ccfsm_target_label).
4 -> 0 final_prescan_insn, if `target' unconditional branch is reached
If the jump clobbers the conditions then we use states 2 and 4.
A similar thing can be done with conditional return insns.
We also handle separating branches from sets of the condition code.
This is done here because knowledge of the ccfsm state is required,
we may not be outputting the branch. */
void
arc_final_prescan_insn (insn, opvec, noperands)
rtx insn;
rtx *opvec;
int noperands;
{
/* BODY will hold the body of INSN. */
register rtx body = PATTERN (insn);
/* This will be 1 if trying to repeat the trick (ie: do the `else' part of
an if/then/else), and things need to be reversed. */
int reverse = 0;
/* If we start with a return insn, we only succeed if we find another one. */
int seeking_return = 0;
/* START_INSN will hold the insn from where we start looking. This is the
first insn after the following code_label if REVERSE is true. */
rtx start_insn = insn;
/* Update compare/branch separation marker. */
record_cc_ref (insn);
/* Allow -mdebug-ccfsm to turn this off so we can see how well it does.
We can't do this in macro FINAL_PRESCAN_INSN because its called from
final_scan_insn which has `optimize' as a local. */
if (optimize < 2 || TARGET_NO_COND_EXEC)
return;
/* If in state 4, check if the target branch is reached, in order to
change back to state 0. */
if (arc_ccfsm_state == 4)
{
if (insn == arc_ccfsm_target_insn)
{
arc_ccfsm_target_insn = NULL;
arc_ccfsm_state = 0;
}
return;
}
/* If in state 3, it is possible to repeat the trick, if this insn is an
unconditional branch to a label, and immediately following this branch
is the previous target label which is only used once, and the label this
branch jumps to is not too far off. Or in other words "we've done the
`then' part, see if we can do the `else' part." */
if (arc_ccfsm_state == 3)
{
if (simplejump_p (insn))
{
start_insn = next_nonnote_insn (start_insn);
if (GET_CODE (start_insn) == BARRIER)
{
/* ??? Isn't this always a barrier? */
start_insn = next_nonnote_insn (start_insn);
}
if (GET_CODE (start_insn) == CODE_LABEL
&& CODE_LABEL_NUMBER (start_insn) == arc_ccfsm_target_label
&& LABEL_NUSES (start_insn) == 1)
reverse = TRUE;
else
return;
}
else if (GET_CODE (body) == RETURN)
{
start_insn = next_nonnote_insn (start_insn);
if (GET_CODE (start_insn) == BARRIER)
start_insn = next_nonnote_insn (start_insn);
if (GET_CODE (start_insn) == CODE_LABEL
&& CODE_LABEL_NUMBER (start_insn) == arc_ccfsm_target_label
&& LABEL_NUSES (start_insn) == 1)
{
reverse = TRUE;
seeking_return = 1;
}
else
return;
}
else
return;
}
if (GET_CODE (insn) != JUMP_INSN)
return;
/* This jump might be paralleled with a clobber of the condition codes,
the jump should always come first. */
if (GET_CODE (body) == PARALLEL && XVECLEN (body, 0) > 0)
body = XVECEXP (body, 0, 0);
if (reverse
|| (GET_CODE (body) == SET && GET_CODE (SET_DEST (body)) == PC
&& GET_CODE (SET_SRC (body)) == IF_THEN_ELSE))
{
int insns_skipped = 0, fail = FALSE, succeed = FALSE;
/* Flag which part of the IF_THEN_ELSE is the LABEL_REF. */
int then_not_else = TRUE;
/* Nonzero if next insn must be the target label. */
int next_must_be_target_label_p;
rtx this_insn = start_insn, label = 0;
/* Register the insn jumped to. */
if (reverse)
{
if (!seeking_return)
label = XEXP (SET_SRC (body), 0);
}
else if (GET_CODE (XEXP (SET_SRC (body), 1)) == LABEL_REF)
label = XEXP (XEXP (SET_SRC (body), 1), 0);
else if (GET_CODE (XEXP (SET_SRC (body), 2)) == LABEL_REF)
{
label = XEXP (XEXP (SET_SRC (body), 2), 0);
then_not_else = FALSE;
}
else if (GET_CODE (XEXP (SET_SRC (body), 1)) == RETURN)
seeking_return = 1;
else if (GET_CODE (XEXP (SET_SRC (body), 2)) == RETURN)
{
seeking_return = 1;
then_not_else = FALSE;
}
else
abort ();
/* See how many insns this branch skips, and what kind of insns. If all
insns are okay, and the label or unconditional branch to the same
label is not too far away, succeed. */
for (insns_skipped = 0, next_must_be_target_label_p = FALSE;
!fail && !succeed && insns_skipped < MAX_INSNS_SKIPPED;
insns_skipped++)
{
rtx scanbody;
this_insn = next_nonnote_insn (this_insn);
if (!this_insn)
break;
if (next_must_be_target_label_p)
{
if (GET_CODE (this_insn) == BARRIER)
continue;
if (GET_CODE (this_insn) == CODE_LABEL
&& this_insn == label)
{
arc_ccfsm_state = 1;
succeed = TRUE;
}
else
fail = TRUE;
break;
}
scanbody = PATTERN (this_insn);
switch (GET_CODE (this_insn))
{
case CODE_LABEL:
/* Succeed if it is the target label, otherwise fail since
control falls in from somewhere else. */
if (this_insn == label)
{
arc_ccfsm_state = 1;
succeed = TRUE;
}
else
fail = TRUE;
break;
case BARRIER:
/* Succeed if the following insn is the target label.
Otherwise fail.
If return insns are used then the last insn in a function
will be a barrier. */
next_must_be_target_label_p = TRUE;
break;
case CALL_INSN:
/* Can handle a call insn if there are no insns after it.
IE: The next "insn" is the target label. We don't have to
worry about delay slots as such insns are SEQUENCE's inside
INSN's. ??? It is possible to handle such insns though. */
if (get_attr_cond (this_insn) == COND_CANUSE)
next_must_be_target_label_p = TRUE;
else
fail = TRUE;
break;
case JUMP_INSN:
/* If this is an unconditional branch to the same label, succeed.
If it is to another label, do nothing. If it is conditional,
fail. */
/* ??? Probably, the test for the SET and the PC are unnecessary. */
if (GET_CODE (scanbody) == SET
&& GET_CODE (SET_DEST (scanbody)) == PC)
{
if (GET_CODE (SET_SRC (scanbody)) == LABEL_REF
&& XEXP (SET_SRC (scanbody), 0) == label && !reverse)
{
arc_ccfsm_state = 2;
succeed = TRUE;
}
else if (GET_CODE (SET_SRC (scanbody)) == IF_THEN_ELSE)
fail = TRUE;
}
else if (GET_CODE (scanbody) == RETURN
&& seeking_return)
{
arc_ccfsm_state = 2;
succeed = TRUE;
}
else if (GET_CODE (scanbody) == PARALLEL)
{
if (get_attr_cond (this_insn) != COND_CANUSE)
fail = TRUE;
}
break;
case INSN:
/* We can only do this with insns that can use the condition
codes (and don't set them). */
if (GET_CODE (scanbody) == SET
|| GET_CODE (scanbody) == PARALLEL)
{
if (get_attr_cond (this_insn) != COND_CANUSE)
fail = TRUE;
}
/* We can't handle other insns like sequences. */
else
fail = TRUE;
break;
default:
break;
}
}
if (succeed)
{
if ((!seeking_return) && (arc_ccfsm_state == 1 || reverse))
arc_ccfsm_target_label = CODE_LABEL_NUMBER (label);
else if (seeking_return || arc_ccfsm_state == 2)
{
while (this_insn && GET_CODE (PATTERN (this_insn)) == USE)
{
this_insn = next_nonnote_insn (this_insn);
if (this_insn && (GET_CODE (this_insn) == BARRIER
|| GET_CODE (this_insn) == CODE_LABEL))
abort ();
}
if (!this_insn)
{
/* Oh dear! we ran off the end, give up. */
insn_extract (insn);
arc_ccfsm_state = 0;
arc_ccfsm_target_insn = NULL;
return;
}
arc_ccfsm_target_insn = this_insn;
}
else
abort ();
/* If REVERSE is true, ARM_CURRENT_CC needs to be inverted from
what it was. */
if (!reverse)
arc_ccfsm_current_cc = get_arc_condition_code (XEXP (SET_SRC (body),
0));
if (reverse || then_not_else)
arc_ccfsm_current_cc = ARC_INVERSE_CONDITION_CODE (arc_ccfsm_current_cc);
}
/* Restore recog_operand. Getting the attributes of other insns can
destroy this array, but final.c assumes that it remains intact
across this call; since the insn has been recognized already we
call insn_extract direct. */
insn_extract (insn);
}
}
/* Record that we are currently outputting label NUM with prefix PREFIX.
It it's the label we're looking for, reset the ccfsm machinery.
Called from ASM_OUTPUT_INTERNAL_LABEL. */
void
arc_ccfsm_at_label (prefix, num)
char *prefix;
int num;
{
if (arc_ccfsm_state == 3 && arc_ccfsm_target_label == num
&& !strcmp (prefix, "L"))
{
arc_ccfsm_state = 0;
arc_ccfsm_target_insn = NULL_RTX;
}
}
/* See if the current insn, which is a conditional branch, is to be
deleted. */
int
arc_ccfsm_branch_deleted_p ()
{
if (arc_ccfsm_state == 1 || arc_ccfsm_state == 2)
return 1;
return 0;
}
/* Record a branch isn't output because subsequent insns can be
conditionalized. */
void
arc_ccfsm_record_branch_deleted ()
{
/* Indicate we're conditionalizing insns now. */
arc_ccfsm_state += 2;
/* If the next insn is a subroutine call, we still need a nop between the
cc setter and user. We need to undo the effect of calling record_cc_ref
for the just deleted branch. */
current_insn_set_cc_p = last_insn_set_cc_p;
}