2387 lines
70 KiB
C
2387 lines
70 KiB
C
/* Subroutines used for code generation on the Argonaut ARC cpu.
|
||
Copyright (C) 1994, 1995, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
|
||
2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc.
|
||
|
||
This file is part of GCC.
|
||
|
||
GCC 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 3, or (at your option)
|
||
any later version.
|
||
|
||
GCC 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 GCC; see the file COPYING3. If not see
|
||
<http://www.gnu.org/licenses/>. */
|
||
|
||
/* ??? This is an old port, and is undoubtedly suffering from bit rot. */
|
||
|
||
#include "config.h"
|
||
#include "system.h"
|
||
#include "coretypes.h"
|
||
#include "tm.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 "output.h"
|
||
#include "insn-attr.h"
|
||
#include "flags.h"
|
||
#include "function.h"
|
||
#include "expr.h"
|
||
#include "recog.h"
|
||
#include "toplev.h"
|
||
#include "df.h"
|
||
#include "tm_p.h"
|
||
#include "target.h"
|
||
#include "target-def.h"
|
||
|
||
/* Which cpu we're compiling for. */
|
||
int arc_cpu_type;
|
||
|
||
/* Name of mangle string to add to symbols to separate code compiled for each
|
||
cpu (or NULL). */
|
||
const char *arc_mangle_cpu;
|
||
|
||
/* Name of text, data, and rodata sections used in varasm.c. */
|
||
const char *arc_text_section;
|
||
const char *arc_data_section;
|
||
const 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 bool arc_handle_option (size_t, const char *, int);
|
||
static void record_cc_ref (rtx);
|
||
static void arc_init_reg_tables (void);
|
||
static int get_arc_condition_code (rtx);
|
||
static tree arc_handle_interrupt_attribute (tree *, tree, tree, int, bool *);
|
||
static bool arc_assemble_integer (rtx, unsigned int, int);
|
||
static void arc_output_function_prologue (FILE *, HOST_WIDE_INT);
|
||
static void arc_output_function_epilogue (FILE *, HOST_WIDE_INT);
|
||
static void arc_file_start (void);
|
||
static void arc_internal_label (FILE *, const char *, unsigned long);
|
||
static void arc_va_start (tree, rtx);
|
||
static void arc_setup_incoming_varargs (CUMULATIVE_ARGS *, enum machine_mode,
|
||
tree, int *, int);
|
||
static bool arc_rtx_costs (rtx, int, int, int *, bool);
|
||
static int arc_address_cost (rtx, bool);
|
||
static void arc_external_libcall (rtx);
|
||
static bool arc_return_in_memory (const_tree, const_tree);
|
||
static bool arc_pass_by_reference (CUMULATIVE_ARGS *, enum machine_mode,
|
||
const_tree, bool);
|
||
static void arc_trampoline_init (rtx, tree, rtx);
|
||
|
||
|
||
/* ARC specific attributs. */
|
||
|
||
static const struct attribute_spec arc_attribute_table[] =
|
||
{
|
||
/* { name, min_len, max_len, decl_req, type_req, fn_type_req, handler } */
|
||
{ "interrupt", 1, 1, true, false, false, arc_handle_interrupt_attribute },
|
||
{ NULL, 0, 0, false, false, false, NULL }
|
||
};
|
||
|
||
/* Initialize the GCC target structure. */
|
||
#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_ASM_INTEGER
|
||
#define TARGET_ASM_INTEGER arc_assemble_integer
|
||
|
||
#undef TARGET_ASM_FUNCTION_PROLOGUE
|
||
#define TARGET_ASM_FUNCTION_PROLOGUE arc_output_function_prologue
|
||
#undef TARGET_ASM_FUNCTION_EPILOGUE
|
||
#define TARGET_ASM_FUNCTION_EPILOGUE arc_output_function_epilogue
|
||
#undef TARGET_ASM_FILE_START
|
||
#define TARGET_ASM_FILE_START arc_file_start
|
||
#undef TARGET_ATTRIBUTE_TABLE
|
||
#define TARGET_ATTRIBUTE_TABLE arc_attribute_table
|
||
#undef TARGET_ASM_INTERNAL_LABEL
|
||
#define TARGET_ASM_INTERNAL_LABEL arc_internal_label
|
||
#undef TARGET_ASM_EXTERNAL_LIBCALL
|
||
#define TARGET_ASM_EXTERNAL_LIBCALL arc_external_libcall
|
||
|
||
#undef TARGET_HANDLE_OPTION
|
||
#define TARGET_HANDLE_OPTION arc_handle_option
|
||
|
||
#undef TARGET_RTX_COSTS
|
||
#define TARGET_RTX_COSTS arc_rtx_costs
|
||
#undef TARGET_ADDRESS_COST
|
||
#define TARGET_ADDRESS_COST arc_address_cost
|
||
|
||
#undef TARGET_PROMOTE_FUNCTION_MODE
|
||
#define TARGET_PROMOTE_FUNCTION_MODE default_promote_function_mode_always_promote
|
||
#undef TARGET_PROMOTE_PROTOTYPES
|
||
#define TARGET_PROMOTE_PROTOTYPES hook_bool_const_tree_true
|
||
|
||
#undef TARGET_RETURN_IN_MEMORY
|
||
#define TARGET_RETURN_IN_MEMORY arc_return_in_memory
|
||
#undef TARGET_PASS_BY_REFERENCE
|
||
#define TARGET_PASS_BY_REFERENCE arc_pass_by_reference
|
||
#undef TARGET_CALLEE_COPIES
|
||
#define TARGET_CALLEE_COPIES hook_bool_CUMULATIVE_ARGS_mode_tree_bool_true
|
||
|
||
#undef TARGET_SETUP_INCOMING_VARARGS
|
||
#define TARGET_SETUP_INCOMING_VARARGS arc_setup_incoming_varargs
|
||
|
||
#undef TARGET_EXPAND_BUILTIN_VA_START
|
||
#define TARGET_EXPAND_BUILTIN_VA_START arc_va_start
|
||
|
||
#undef TARGET_TRAMPOLINE_INIT
|
||
#define TARGET_TRAMPOLINE_INIT arc_trampoline_init
|
||
|
||
struct gcc_target targetm = TARGET_INITIALIZER;
|
||
|
||
/* Implement TARGET_HANDLE_OPTION. */
|
||
|
||
static bool
|
||
arc_handle_option (size_t code, const char *arg, int value ATTRIBUTE_UNUSED)
|
||
{
|
||
switch (code)
|
||
{
|
||
case OPT_mcpu_:
|
||
return strcmp (arg, "base") == 0 || ARC_EXTENSION_CPU (arg);
|
||
|
||
default:
|
||
return true;
|
||
}
|
||
}
|
||
|
||
/* Called by OVERRIDE_OPTIONS to initialize various things. */
|
||
|
||
void
|
||
arc_init (void)
|
||
{
|
||
char *tmp;
|
||
|
||
/* Set the pseudo-ops for the various standard sections. */
|
||
arc_text_section = tmp = XNEWVEC (char, strlen (arc_text_string) + sizeof (ARC_SECTION_FORMAT) + 1);
|
||
sprintf (tmp, ARC_SECTION_FORMAT, arc_text_string);
|
||
arc_data_section = tmp = XNEWVEC (char, strlen (arc_data_string) + sizeof (ARC_SECTION_FORMAT) + 1);
|
||
sprintf (tmp, ARC_SECTION_FORMAT, arc_data_string);
|
||
arc_rodata_section = tmp = XNEWVEC (char, strlen (arc_rodata_string) + sizeof (ARC_SECTION_FORMAT) + 1);
|
||
sprintf (tmp, 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 const char *const 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 (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 : gcc_unreachable ();
|
||
}
|
||
/*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 (enum rtx_code op,
|
||
rtx x ATTRIBUTE_UNUSED,
|
||
rtx y ATTRIBUTE_UNUSED)
|
||
{
|
||
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;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
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. */
|
||
|
||
const 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];
|
||
|
||
static void
|
||
arc_init_reg_tables (void)
|
||
{
|
||
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:
|
||
arc_mode_class[i] = 1 << (int) C_MODE;
|
||
break;
|
||
default:
|
||
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
|
||
*/
|
||
|
||
/* Handle an "interrupt" attribute; arguments as in
|
||
struct attribute_spec.handler. */
|
||
static tree
|
||
arc_handle_interrupt_attribute (tree *node ATTRIBUTE_UNUSED,
|
||
tree name,
|
||
tree args,
|
||
int flags ATTRIBUTE_UNUSED,
|
||
bool *no_add_attrs)
|
||
{
|
||
tree value = TREE_VALUE (args);
|
||
|
||
if (TREE_CODE (value) != STRING_CST)
|
||
{
|
||
warning (OPT_Wattributes,
|
||
"argument of %qE attribute is not a string constant",
|
||
name);
|
||
*no_add_attrs = true;
|
||
}
|
||
else if (strcmp (TREE_STRING_POINTER (value), "ilink1")
|
||
&& strcmp (TREE_STRING_POINTER (value), "ilink2"))
|
||
{
|
||
warning (OPT_Wattributes,
|
||
"argument of %qE attribute is not \"ilink1\" or \"ilink2\"",
|
||
name);
|
||
*no_add_attrs = true;
|
||
}
|
||
|
||
return NULL_TREE;
|
||
}
|
||
|
||
|
||
/* Acceptable arguments to the call insn. */
|
||
|
||
int
|
||
call_address_operand (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 (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 (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED)
|
||
{
|
||
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 (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED)
|
||
{
|
||
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 (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED)
|
||
{
|
||
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 (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED)
|
||
{
|
||
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;
|
||
default:
|
||
break;
|
||
}
|
||
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 (rtx op,
|
||
enum machine_mode mode ATTRIBUTE_UNUSED)
|
||
{
|
||
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;
|
||
default:
|
||
break;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* Return true if OP is an acceptable argument for a single word
|
||
move source. */
|
||
|
||
int
|
||
move_src_operand (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 (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 (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 (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 (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 (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 (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED)
|
||
{
|
||
/* 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 (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED)
|
||
{
|
||
#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 (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED)
|
||
{
|
||
enum rtx_code code;
|
||
if (!COMPARISON_P (op))
|
||
return 0;
|
||
|
||
code = GET_CODE (op);
|
||
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. Return the rtx
|
||
for the cc reg in the proper mode. */
|
||
|
||
rtx
|
||
gen_compare_reg (enum rtx_code code, rtx x, rtx y)
|
||
{
|
||
enum machine_mode mode = SELECT_CC_MODE (code, x, y);
|
||
return gen_rtx_REG (mode, 61);
|
||
}
|
||
|
||
/* 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 (rtx value)
|
||
{
|
||
HOST_WIDE_INT low, high;
|
||
|
||
gcc_assert (GET_CODE (value) == CONST_DOUBLE);
|
||
|
||
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_start
|
||
to compensate. */
|
||
|
||
static void
|
||
arc_setup_incoming_varargs (CUMULATIVE_ARGS *cum,
|
||
enum machine_mode mode,
|
||
tree type ATTRIBUTE_UNUSED,
|
||
int *pretend_size,
|
||
int no_rtl)
|
||
{
|
||
int first_anon_arg;
|
||
|
||
/* All BLKmode values are passed by reference. */
|
||
gcc_assert (mode != BLKmode);
|
||
|
||
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));
|
||
set_mem_alias_set (regblock, get_varargs_alias_set ());
|
||
set_mem_align (regblock, BITS_PER_WORD);
|
||
move_block_from_reg (first_reg_offset, regblock,
|
||
MAX_ARC_PARM_REGS - first_reg_offset);
|
||
|
||
*pretend_size = ((MAX_ARC_PARM_REGS - first_reg_offset + align_slop)
|
||
* UNITS_PER_WORD);
|
||
}
|
||
}
|
||
|
||
/* Cost functions. */
|
||
|
||
/* Compute a (partial) cost for rtx X. Return true if the complete
|
||
cost has been computed, and false if subexpressions should be
|
||
scanned. In either case, *TOTAL contains the cost result. */
|
||
|
||
static bool
|
||
arc_rtx_costs (rtx x, int code, int outer_code ATTRIBUTE_UNUSED, int *total,
|
||
bool speed ATTRIBUTE_UNUSED)
|
||
{
|
||
switch (code)
|
||
{
|
||
/* Small integers are as cheap as registers. 4 byte values can
|
||
be fetched as immediate constants - let's give that the cost
|
||
of an extra insn. */
|
||
case CONST_INT:
|
||
if (SMALL_INT (INTVAL (x)))
|
||
{
|
||
*total = 0;
|
||
return true;
|
||
}
|
||
/* FALLTHRU */
|
||
|
||
case CONST:
|
||
case LABEL_REF:
|
||
case SYMBOL_REF:
|
||
*total = COSTS_N_INSNS (1);
|
||
return true;
|
||
|
||
case CONST_DOUBLE:
|
||
{
|
||
rtx high, low;
|
||
split_double (x, &high, &low);
|
||
*total = COSTS_N_INSNS (!SMALL_INT (INTVAL (high))
|
||
+ !SMALL_INT (INTVAL (low)));
|
||
return true;
|
||
}
|
||
|
||
/* Encourage synth_mult to find a synthetic multiply when reasonable.
|
||
If we need more than 12 insns to do a multiply, then go out-of-line,
|
||
since the call overhead will be < 10% of the cost of the multiply. */
|
||
case ASHIFT:
|
||
case ASHIFTRT:
|
||
case LSHIFTRT:
|
||
if (TARGET_SHIFTER)
|
||
*total = COSTS_N_INSNS (1);
|
||
else if (GET_CODE (XEXP (x, 1)) != CONST_INT)
|
||
*total = COSTS_N_INSNS (16);
|
||
else
|
||
*total = COSTS_N_INSNS (INTVAL (XEXP ((x), 1)));
|
||
return false;
|
||
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
|
||
/* Provide the costs of an addressing mode that contains ADDR.
|
||
If ADDR is not a valid address, its cost is irrelevant. */
|
||
|
||
static int
|
||
arc_address_cost (rtx addr, bool speed ATTRIBUTE_UNUSED)
|
||
{
|
||
switch (GET_CODE (addr))
|
||
{
|
||
case REG :
|
||
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 (INTVAL (plus1)) ? 1 : 2;
|
||
case CONST :
|
||
case SYMBOL_REF :
|
||
case LABEL_REF :
|
||
return 2;
|
||
default:
|
||
break;
|
||
}
|
||
break;
|
||
}
|
||
default:
|
||
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 (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_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
|
||
gcc_unreachable ();
|
||
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 \
|
||
&& (df_regs_ever_live_p (regno) && (!call_used_regs[regno] || interrupt_p)))
|
||
|
||
#define MUST_SAVE_RETURN_ADDR (df_regs_ever_live_p (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 (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 = crtl->outgoing_args_size;
|
||
pretend_size = crtl->args.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) + crtl->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 *file,
|
||
const char *base_reg,
|
||
unsigned int offset,
|
||
unsigned int gmask,
|
||
const 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;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Target hook to assemble an integer object. The ARC version needs to
|
||
emit a special directive for references to labels and function
|
||
symbols. */
|
||
|
||
static bool
|
||
arc_assemble_integer (rtx x, unsigned int size, int aligned_p)
|
||
{
|
||
if (size == UNITS_PER_WORD && aligned_p
|
||
&& ((GET_CODE (x) == SYMBOL_REF && SYMBOL_REF_FUNCTION_P (x))
|
||
|| GET_CODE (x) == LABEL_REF))
|
||
{
|
||
fputs ("\t.word\t%st(", asm_out_file);
|
||
output_addr_const (asm_out_file, x);
|
||
fputs (")\n", asm_out_file);
|
||
return true;
|
||
}
|
||
return default_assemble_integer (x, size, aligned_p);
|
||
}
|
||
|
||
/* Set up the stack and frame pointer (if desired) for the function. */
|
||
|
||
static void
|
||
arc_output_function_prologue (FILE *file, HOST_WIDE_INT size)
|
||
{
|
||
const char *sp_str = reg_names[STACK_POINTER_REGNUM];
|
||
const 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. */
|
||
gcc_assert (size || !gmask);
|
||
|
||
/* 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," HOST_WIDE_INT_PRINT_DEC "\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. */
|
||
|
||
static void
|
||
arc_output_function_epilogue (FILE *file, HOST_WIDE_INT size)
|
||
{
|
||
rtx epilogue_delay = crtl->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 = !cfun->calls_alloca;
|
||
const char *sp_str = reg_names[STACK_POINTER_REGNUM];
|
||
const 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)
|
||
{
|
||
gcc_assert (frame_pointer_needed);
|
||
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, 1, NULL);
|
||
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, 1, NULL);
|
||
}
|
||
}
|
||
|
||
/* Emit the return instruction. */
|
||
{
|
||
static const int regs[4] = {
|
||
0, RETURN_ADDR_REGNUM, ILINK1_REGNUM, ILINK2_REGNUM
|
||
};
|
||
|
||
/* Update the flags, if returning from an interrupt handler. */
|
||
if (ARC_INTERRUPT_P (fn_type))
|
||
fprintf (file, "\tj.d.f %s\n", reg_names[regs[fn_type]]);
|
||
else
|
||
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)
|
||
{
|
||
gcc_assert (!frame_pointer_needed || fp_restored_p);
|
||
gcc_assert (restored >= size);
|
||
final_scan_insn (XEXP (epilogue_delay, 0), file, 1, 1, NULL);
|
||
}
|
||
else if (frame_pointer_needed && !fp_restored_p)
|
||
{
|
||
gcc_assert (SMALL_INT (frame_size));
|
||
/* 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)
|
||
{
|
||
gcc_assert (SMALL_INT (size - restored));
|
||
fprintf (file, "\tadd %s,%s," HOST_WIDE_INT_PRINT_DEC "\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 (void)
|
||
{
|
||
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 (rtx trial, int slot)
|
||
{
|
||
gcc_assert (!slot);
|
||
|
||
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;
|
||
}
|
||
|
||
/* Return true if OP is a shift operator. */
|
||
|
||
int
|
||
shift_operator (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED)
|
||
{
|
||
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. */
|
||
|
||
const char *
|
||
output_shift (rtx *operands)
|
||
{
|
||
rtx shift = operands[3];
|
||
enum machine_mode mode = GET_MODE (shift);
|
||
enum rtx_code code = GET_CODE (shift);
|
||
const char *shift_one;
|
||
|
||
gcc_assert (mode == SImode);
|
||
|
||
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: gcc_unreachable ();
|
||
}
|
||
|
||
if (GET_CODE (operands[2]) != CONST_INT)
|
||
{
|
||
if (optimize)
|
||
{
|
||
output_asm_insn ("sub.f 0,%2,0", operands);
|
||
output_asm_insn ("mov lp_count,%2", operands);
|
||
output_asm_insn ("bz 2f", operands);
|
||
}
|
||
else
|
||
output_asm_insn ("mov %4,%2", operands);
|
||
goto shiftloop;
|
||
}
|
||
else
|
||
{
|
||
int n;
|
||
|
||
/* If the count is negative, make it 0. */
|
||
n = INTVAL (operands[2]);
|
||
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;
|
||
default:
|
||
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 (buf, "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)
|
||
fprintf (asm_out_file, "\t%s single insn loop\n",
|
||
ASM_COMMENT_START);
|
||
else
|
||
fprintf (asm_out_file, "1:\t%s single insn loop\n",
|
||
ASM_COMMENT_START);
|
||
output_asm_insn (shift_one, operands);
|
||
fprintf (asm_out_file, "2:\t%s end single insn loop\n",
|
||
ASM_COMMENT_START);
|
||
}
|
||
else
|
||
{
|
||
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);
|
||
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 (rtx tramp ATTRIBUTE_UNUSED,
|
||
rtx fnaddr ATTRIBUTE_UNUSED,
|
||
rtx cxt ATTRIBUTE_UNUSED)
|
||
{
|
||
}
|
||
|
||
/* Set the cpu type and print out other fancy things,
|
||
at the top of the file. */
|
||
|
||
static void
|
||
arc_file_start (void)
|
||
{
|
||
default_file_start ();
|
||
fprintf (asm_out_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 *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_FUNCTION_P (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",
|
||
(long)(code == 'L' ? INTVAL (first) : INTVAL (second)));
|
||
}
|
||
else
|
||
output_operand_lossage ("invalid operand to %%H/%%L code");
|
||
return;
|
||
case 'A' :
|
||
{
|
||
char str[30];
|
||
|
||
gcc_assert (GET_CODE (x) == CONST_DOUBLE
|
||
&& GET_MODE_CLASS (GET_MODE (x)) == MODE_FLOAT);
|
||
|
||
real_to_decimal (str, CONST_DOUBLE_REAL_VALUE (x), sizeof (str), 0, 1);
|
||
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 *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_FUNCTION_P (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);
|
||
gcc_assert (GET_CODE (base) == REG);
|
||
fputs (reg_names[REGNO (base)], file);
|
||
if (index == 0)
|
||
{
|
||
if (offset != 0)
|
||
fprintf (file, ",%d", offset);
|
||
}
|
||
else
|
||
{
|
||
switch (GET_CODE (index))
|
||
{
|
||
case REG:
|
||
fprintf (file, ",%s", reg_names[REGNO (index)]);
|
||
break;
|
||
case SYMBOL_REF:
|
||
fputc (',', file), output_addr_const (file, index);
|
||
break;
|
||
default:
|
||
gcc_unreachable ();
|
||
}
|
||
}
|
||
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. */
|
||
gcc_unreachable ();
|
||
break;
|
||
default :
|
||
output_addr_const (file, addr);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* Update compare/branch separation marker. */
|
||
|
||
static void
|
||
record_cc_ref (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 (*targetm.asm_out.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 (rtx insn,
|
||
rtx *opvec ATTRIBUTE_UNUSED,
|
||
int noperands ATTRIBUTE_UNUSED)
|
||
{
|
||
/* BODY will hold the body of INSN. */
|
||
register rtx body = PATTERN (insn);
|
||
|
||
/* This will be 1 if trying to repeat the trick (i.e.: 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
|
||
gcc_unreachable ();
|
||
|
||
/* 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
|
||
{
|
||
gcc_assert (seeking_return || arc_ccfsm_state == 2);
|
||
while (this_insn && GET_CODE (PATTERN (this_insn)) == USE)
|
||
{
|
||
this_insn = next_nonnote_insn (this_insn);
|
||
gcc_assert (!this_insn
|
||
|| (GET_CODE (this_insn) != BARRIER
|
||
&& GET_CODE (this_insn) != CODE_LABEL));
|
||
}
|
||
if (!this_insn)
|
||
{
|
||
/* Oh dear! we ran off the end, give up. */
|
||
extract_insn_cached (insn);
|
||
arc_ccfsm_state = 0;
|
||
arc_ccfsm_target_insn = NULL;
|
||
return;
|
||
}
|
||
arc_ccfsm_target_insn = this_insn;
|
||
}
|
||
|
||
/* 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_data. Getting the attributes of other insns can
|
||
destroy this array, but final.c assumes that it remains intact
|
||
across this call. */
|
||
extract_insn_cached (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 (*targetm.asm_out.internal_label). */
|
||
|
||
void
|
||
arc_ccfsm_at_label (const 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 (void)
|
||
{
|
||
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 (void)
|
||
{
|
||
/* 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;
|
||
}
|
||
|
||
static void
|
||
arc_va_start (tree valist, rtx nextarg)
|
||
{
|
||
/* See arc_setup_incoming_varargs for reasons for this oddity. */
|
||
if (crtl->args.info < 8
|
||
&& (crtl->args.info & 1))
|
||
nextarg = plus_constant (nextarg, UNITS_PER_WORD);
|
||
|
||
std_expand_builtin_va_start (valist, nextarg);
|
||
}
|
||
|
||
/* This is how to output a definition of an internal numbered label where
|
||
PREFIX is the class of label and NUM is the number within the class. */
|
||
|
||
static void
|
||
arc_internal_label (FILE *stream, const char *prefix, unsigned long labelno)
|
||
{
|
||
arc_ccfsm_at_label (prefix, labelno);
|
||
default_internal_label (stream, prefix, labelno);
|
||
}
|
||
|
||
/* Worker function for TARGET_ASM_EXTERNAL_LIBCALL. */
|
||
|
||
static void
|
||
arc_external_libcall (rtx fun ATTRIBUTE_UNUSED)
|
||
{
|
||
#if 0
|
||
/* On the ARC we want to have libgcc's for multiple cpus in one binary.
|
||
We can't use `assemble_name' here as that will call ASM_OUTPUT_LABELREF
|
||
and we'll get another suffix added on if -mmangle-cpu. */
|
||
if (TARGET_MANGLE_CPU_LIBGCC)
|
||
{
|
||
fprintf (FILE, "\t.rename\t_%s, _%s%s\n",
|
||
XSTR (SYMREF, 0), XSTR (SYMREF, 0),
|
||
arc_mangle_suffix);
|
||
}
|
||
#endif
|
||
}
|
||
|
||
/* Worker function for TARGET_RETURN_IN_MEMORY. */
|
||
|
||
static bool
|
||
arc_return_in_memory (const_tree type, const_tree fntype ATTRIBUTE_UNUSED)
|
||
{
|
||
if (AGGREGATE_TYPE_P (type))
|
||
return true;
|
||
else
|
||
{
|
||
HOST_WIDE_INT size = int_size_in_bytes (type);
|
||
return (size == -1 || size > 8);
|
||
}
|
||
}
|
||
|
||
/* For ARC, All aggregates and arguments greater than 8 bytes are
|
||
passed by reference. */
|
||
|
||
static bool
|
||
arc_pass_by_reference (CUMULATIVE_ARGS *ca ATTRIBUTE_UNUSED,
|
||
enum machine_mode mode, const_tree type,
|
||
bool named ATTRIBUTE_UNUSED)
|
||
{
|
||
unsigned HOST_WIDE_INT size;
|
||
|
||
if (type)
|
||
{
|
||
if (AGGREGATE_TYPE_P (type))
|
||
return true;
|
||
size = int_size_in_bytes (type);
|
||
}
|
||
else
|
||
size = GET_MODE_SIZE (mode);
|
||
|
||
return size > 8;
|
||
}
|
||
|
||
/* Trampolines. */
|
||
/* ??? This doesn't work yet because GCC will use as the address of a nested
|
||
function the address of the trampoline. We need to use that address
|
||
right shifted by 2. It looks like we'll need PSImode after all. :-(
|
||
|
||
??? The above comment sounds like it's doable via
|
||
TARGET_TRAMPOLINE_ADJUST_ADDRESS; no PSImode needed.
|
||
|
||
On the ARC, the trampoline is quite simple as we have 32-bit immediate
|
||
constants.
|
||
|
||
mov r24,STATIC
|
||
j.nd FUNCTION
|
||
*/
|
||
|
||
static void
|
||
arc_trampoline_init (rtx m_tramp, tree fndecl, rtx chain_value)
|
||
{
|
||
rtx fnaddr = XEXP (DECL_RTL (fndecl), 0);
|
||
rtx mem;
|
||
|
||
mem = adjust_address (m_tramp, SImode, 0);
|
||
emit_move_insn (mem, GEN_INT (0x631f7c00));
|
||
|
||
mem = adjust_address (m_tramp, SImode, 4);
|
||
emit_move_insn (mem, chain_value);
|
||
|
||
mem = adjust_address (m_tramp, SImode, 8);
|
||
emit_move_insn (mem, GEN_INT (0x381f0000));
|
||
|
||
mem = adjust_address (m_tramp, SImode, 12);
|
||
emit_move_insn (mem, fnaddr);
|
||
|
||
emit_insn (gen_flush_icache (m_tramp));
|
||
}
|