9789 lines
262 KiB
C
9789 lines
262 KiB
C
/* Copyright (C) 1997, 1998, 1999, 2000, 2001 Free Software Foundation, Inc.
|
||
Contributed by Red Hat, Inc.
|
||
|
||
This file is part of GNU CC.
|
||
|
||
GNU CC is free software; you can redistribute it and/or modify
|
||
it under the terms of the GNU General Public License as published by
|
||
the Free Software Foundation; either version 2, or (at your option)
|
||
any later version.
|
||
|
||
GNU CC is distributed in the hope that it will be useful,
|
||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
GNU General Public License for more details.
|
||
|
||
You should have received a copy of the GNU General Public License
|
||
along with GNU CC; see the file COPYING. If not, write to
|
||
the Free Software Foundation, 59 Temple Place - Suite 330,
|
||
Boston, MA 02111-1307, USA. */
|
||
|
||
#include "config.h"
|
||
#include "system.h"
|
||
#include "rtl.h"
|
||
#include "tree.h"
|
||
#include "regs.h"
|
||
#include "hard-reg-set.h"
|
||
#include "real.h"
|
||
#include "insn-config.h"
|
||
#include "conditions.h"
|
||
#include "insn-flags.h"
|
||
#include "output.h"
|
||
#include "insn-attr.h"
|
||
#include "flags.h"
|
||
#include "recog.h"
|
||
#include "reload.h"
|
||
#include "expr.h"
|
||
#include "obstack.h"
|
||
#include "except.h"
|
||
#include "function.h"
|
||
#include "optabs.h"
|
||
#include "toplev.h"
|
||
#include "basic-block.h"
|
||
#include "tm_p.h"
|
||
#include "ggc.h"
|
||
#include <ctype.h>
|
||
#include "target.h"
|
||
#include "target-def.h"
|
||
|
||
#ifndef FRV_INLINE
|
||
#define FRV_INLINE inline
|
||
#endif
|
||
|
||
/* Temporary register allocation support structure. */
|
||
typedef struct frv_tmp_reg_struct
|
||
{
|
||
HARD_REG_SET regs; /* possible registers to allocate */
|
||
int next_reg[N_REG_CLASSES]; /* next register to allocate per class */
|
||
}
|
||
frv_tmp_reg_t;
|
||
|
||
/* Register state information for VLIW re-packing phase. These values must fit
|
||
within an unsigned char. */
|
||
#define REGSTATE_DEAD 0x00 /* register is currently dead */
|
||
#define REGSTATE_CC_MASK 0x07 /* Mask to isolate CCn for cond exec */
|
||
#define REGSTATE_LIVE 0x08 /* register is live */
|
||
#define REGSTATE_MODIFIED 0x10 /* reg modified in current VLIW insn */
|
||
#define REGSTATE_IF_TRUE 0x20 /* reg modified in cond exec true */
|
||
#define REGSTATE_IF_FALSE 0x40 /* reg modified in cond exec false */
|
||
#define REGSTATE_UNUSED 0x80 /* bit for hire */
|
||
#define REGSTATE_MASK 0xff /* mask for the bits to set */
|
||
|
||
/* conditional expression used */
|
||
#define REGSTATE_IF_EITHER (REGSTATE_IF_TRUE | REGSTATE_IF_FALSE)
|
||
|
||
/* the following is not sure in the reg_state bytes, so can have a larger value
|
||
than 0xff. */
|
||
#define REGSTATE_CONDJUMP 0x100 /* conditional jump done in VLIW insn */
|
||
|
||
/* Used in frv_frame_accessor_t to indicate the direction of a register-to-
|
||
memory move. */
|
||
enum frv_stack_op
|
||
{
|
||
FRV_LOAD,
|
||
FRV_STORE
|
||
};
|
||
|
||
/* Information required by frv_frame_access. */
|
||
typedef struct
|
||
{
|
||
/* This field is FRV_LOAD if registers are to be loaded from the stack and
|
||
FRV_STORE if they should be stored onto the stack. FRV_STORE implies
|
||
the move is being done by the prologue code while FRV_LOAD implies it
|
||
is being done by the epilogue. */
|
||
enum frv_stack_op op;
|
||
|
||
/* The base register to use when accessing the stack. This may be the
|
||
frame pointer, stack pointer, or a temporary. The choice of register
|
||
depends on which part of the frame is being accessed and how big the
|
||
frame is. */
|
||
rtx base;
|
||
|
||
/* The offset of BASE from the bottom of the current frame, in bytes. */
|
||
int base_offset;
|
||
} frv_frame_accessor_t;
|
||
|
||
/* Define the information needed to generate branch and scc insns. This is
|
||
stored from the compare operation. */
|
||
rtx frv_compare_op0;
|
||
rtx frv_compare_op1;
|
||
|
||
/* Conditional execution support gathered together in one structure */
|
||
typedef struct
|
||
{
|
||
/* Linked list of insns to add if the conditional execution conversion was
|
||
successful. Each link points to an EXPR_LIST which points to the pattern
|
||
of the insn to add, and the insn to be inserted before. */
|
||
rtx added_insns_list;
|
||
|
||
/* Identify which registers are safe to allocate for if conversions to
|
||
conditional execution. We keep the last allocated register in the
|
||
register classes between COND_EXEC statements. This will mean we allocate
|
||
different registers for each different COND_EXEC group if we can. This
|
||
might allow the scheduler to intermix two different COND_EXEC sections. */
|
||
frv_tmp_reg_t tmp_reg;
|
||
|
||
/* For nested IFs, identify which CC registers are used outside of setting
|
||
via a compare isnsn, and using via a check insn. This will allow us to
|
||
know if we can rewrite the register to use a different register that will
|
||
be paired with the CR register controlling the nested IF-THEN blocks. */
|
||
HARD_REG_SET nested_cc_ok_rewrite;
|
||
|
||
/* Temporary registers allocated to hold constants during conditional
|
||
execution. */
|
||
rtx scratch_regs[FIRST_PSEUDO_REGISTER];
|
||
|
||
/* Current number of temp registers available. */
|
||
int cur_scratch_regs;
|
||
|
||
/* Number of nested conditional execution blocks */
|
||
int num_nested_cond_exec;
|
||
|
||
/* Map of insns that set up constants in scratch registers. */
|
||
bitmap scratch_insns_bitmap;
|
||
|
||
/* Conditional execution test register (CC0..CC7) */
|
||
rtx cr_reg;
|
||
|
||
/* Conditional execution compare register that is paired with cr_reg, so that
|
||
nested compares can be done. The csubcc and caddcc instructions don't
|
||
have enough bits to specify both a CC register to be set and a CR register
|
||
to do the test on, so the same bit number is used for both. Needless to
|
||
say, this is rather inconvient for GCC. */
|
||
rtx nested_cc_reg;
|
||
|
||
/* Extra CR registers used for &&, ||. */
|
||
rtx extra_int_cr;
|
||
rtx extra_fp_cr;
|
||
|
||
/* Previous CR used in nested if, to make sure we are dealing with the same
|
||
nested if as the previous statement. */
|
||
rtx last_nested_if_cr;
|
||
}
|
||
frv_ifcvt_t;
|
||
|
||
static /* GTY(()) */ frv_ifcvt_t frv_ifcvt;
|
||
|
||
/* Map register number to smallest register class. */
|
||
enum reg_class regno_reg_class[FIRST_PSEUDO_REGISTER];
|
||
|
||
/* Map class letter into register class */
|
||
enum reg_class reg_class_from_letter[256];
|
||
|
||
/* Cached value of frv_stack_info */
|
||
static frv_stack_t *frv_stack_cache = (frv_stack_t *)0;
|
||
|
||
/* -mbranch-cost= support */
|
||
const char *frv_branch_cost_string;
|
||
int frv_branch_cost_int = DEFAULT_BRANCH_COST;
|
||
|
||
/* -mcpu= support */
|
||
const char *frv_cpu_string; /* -mcpu= option */
|
||
frv_cpu_t frv_cpu_type = CPU_TYPE; /* value of -mcpu= */
|
||
|
||
/* -mcond-exec-insns= support */
|
||
const char *frv_condexec_insns_str; /* -mcond-exec-insns= option */
|
||
int frv_condexec_insns = DEFAULT_CONDEXEC_INSNS; /* value of -mcond-exec-insns*/
|
||
|
||
/* -mcond-exec-temps= support */
|
||
const char *frv_condexec_temps_str; /* -mcond-exec-temps= option */
|
||
int frv_condexec_temps = DEFAULT_CONDEXEC_TEMPS; /* value of -mcond-exec-temps*/
|
||
|
||
/* -msched-lookahead=n */
|
||
const char *frv_sched_lookahead_str; /* -msched-lookahead=n */
|
||
int frv_sched_lookahead = 4; /* -msched-lookahead=n */
|
||
|
||
/* Forward references */
|
||
static int frv_default_flags_for_cpu PARAMS ((void));
|
||
static int frv_string_begins_with PARAMS ((tree, const char *));
|
||
static FRV_INLINE int symbol_ref_small_data_p PARAMS ((rtx));
|
||
static FRV_INLINE int const_small_data_p PARAMS ((rtx));
|
||
static FRV_INLINE int plus_small_data_p PARAMS ((rtx, rtx));
|
||
static void frv_print_operand_memory_reference_reg
|
||
PARAMS ((FILE *, rtx));
|
||
static void frv_print_operand_memory_reference PARAMS ((FILE *, rtx, int));
|
||
static int frv_print_operand_jump_hint PARAMS ((rtx));
|
||
static FRV_INLINE int frv_regno_ok_for_base_p PARAMS ((int, int));
|
||
static rtx single_set_pattern PARAMS ((rtx));
|
||
static int frv_function_contains_far_jump PARAMS ((void));
|
||
static rtx frv_alloc_temp_reg PARAMS ((frv_tmp_reg_t *,
|
||
enum reg_class,
|
||
enum machine_mode,
|
||
int, int));
|
||
static rtx frv_frame_offset_rtx PARAMS ((int));
|
||
static rtx frv_frame_mem PARAMS ((enum machine_mode,
|
||
rtx, int));
|
||
static rtx frv_dwarf_store PARAMS ((rtx, int));
|
||
static void frv_frame_insn PARAMS ((rtx, rtx));
|
||
static void frv_frame_access PARAMS ((frv_frame_accessor_t*,
|
||
rtx, int));
|
||
static void frv_frame_access_multi PARAMS ((frv_frame_accessor_t*,
|
||
frv_stack_t *, int));
|
||
static void frv_frame_access_standard_regs PARAMS ((enum frv_stack_op,
|
||
frv_stack_t *));
|
||
static struct machine_function *frv_init_machine_status PARAMS ((void));
|
||
static int frv_legitimate_memory_operand PARAMS ((rtx,
|
||
enum machine_mode,
|
||
int));
|
||
static rtx frv_int_to_acc PARAMS ((enum insn_code,
|
||
int, rtx));
|
||
static enum machine_mode frv_matching_accg_mode PARAMS ((enum machine_mode));
|
||
static rtx frv_read_argument PARAMS ((tree *));
|
||
static int frv_check_constant_argument PARAMS ((enum insn_code,
|
||
int, rtx));
|
||
static rtx frv_legitimize_target PARAMS ((enum insn_code, rtx));
|
||
static rtx frv_legitimize_argument PARAMS ((enum insn_code,
|
||
int, rtx));
|
||
static rtx frv_expand_set_builtin PARAMS ((enum insn_code,
|
||
tree, rtx));
|
||
static rtx frv_expand_unop_builtin PARAMS ((enum insn_code,
|
||
tree, rtx));
|
||
static rtx frv_expand_binop_builtin PARAMS ((enum insn_code,
|
||
tree, rtx));
|
||
static rtx frv_expand_cut_builtin PARAMS ((enum insn_code,
|
||
tree, rtx));
|
||
static rtx frv_expand_binopimm_builtin PARAMS ((enum insn_code,
|
||
tree, rtx));
|
||
static rtx frv_expand_voidbinop_builtin PARAMS ((enum insn_code,
|
||
tree));
|
||
static rtx frv_expand_voidtriop_builtin PARAMS ((enum insn_code,
|
||
tree));
|
||
static rtx frv_expand_voidaccop_builtin PARAMS ((enum insn_code,
|
||
tree));
|
||
static rtx frv_expand_mclracc_builtin PARAMS ((tree));
|
||
static rtx frv_expand_mrdacc_builtin PARAMS ((enum insn_code,
|
||
tree));
|
||
static rtx frv_expand_mwtacc_builtin PARAMS ((enum insn_code,
|
||
tree));
|
||
static rtx frv_expand_noargs_builtin PARAMS ((enum insn_code));
|
||
static rtx frv_emit_comparison PARAMS ((enum rtx_code, rtx,
|
||
rtx));
|
||
static int frv_clear_registers_used PARAMS ((rtx *, void *));
|
||
static void frv_ifcvt_add_insn PARAMS ((rtx, rtx, int));
|
||
static rtx frv_ifcvt_rewrite_mem PARAMS ((rtx,
|
||
enum machine_mode,
|
||
rtx));
|
||
static rtx frv_ifcvt_load_value PARAMS ((rtx, rtx));
|
||
static void frv_registers_update PARAMS ((rtx, unsigned char [],
|
||
int [], int *, int));
|
||
static int frv_registers_used_p PARAMS ((rtx, unsigned char [],
|
||
int));
|
||
static int frv_registers_set_p PARAMS ((rtx, unsigned char [],
|
||
int));
|
||
static void frv_pack_insns PARAMS ((void));
|
||
static void frv_function_prologue PARAMS ((FILE *, HOST_WIDE_INT));
|
||
static void frv_function_epilogue PARAMS ((FILE *, HOST_WIDE_INT));
|
||
static bool frv_assemble_integer PARAMS ((rtx, unsigned, int));
|
||
static const char * frv_strip_name_encoding PARAMS ((const char *));
|
||
static void frv_encode_section_info PARAMS ((tree, int));
|
||
static void frv_init_builtins PARAMS ((void));
|
||
static rtx frv_expand_builtin PARAMS ((tree, rtx, rtx, enum machine_mode, int));
|
||
static bool frv_in_small_data_p PARAMS ((tree));
|
||
static void frv_asm_output_mi_thunk
|
||
PARAMS ((FILE *, tree, HOST_WIDE_INT, HOST_WIDE_INT, tree));
|
||
|
||
/* Initialize the GCC target structure. */
|
||
#undef TARGET_ASM_FUNCTION_PROLOGUE
|
||
#define TARGET_ASM_FUNCTION_PROLOGUE frv_function_prologue
|
||
#undef TARGET_ASM_FUNCTION_EPILOGUE
|
||
#define TARGET_ASM_FUNCTION_EPILOGUE frv_function_epilogue
|
||
#undef TARGET_ASM_INTEGER
|
||
#define TARGET_ASM_INTEGER frv_assemble_integer
|
||
#undef TARGET_STRIP_NAME_ENCODING
|
||
#define TARGET_STRIP_NAME_ENCODING frv_strip_name_encoding
|
||
#undef TARGET_ENCODE_SECTION_INFO
|
||
#define TARGET_ENCODE_SECTION_INFO frv_encode_section_info
|
||
#undef TARGET_INIT_BUILTINS
|
||
#define TARGET_INIT_BUILTINS frv_init_builtins
|
||
#undef TARGET_EXPAND_BUILTIN
|
||
#define TARGET_EXPAND_BUILTIN frv_expand_builtin
|
||
#undef TARGET_IN_SMALL_DATA_P
|
||
#define TARGET_IN_SMALL_DATA_P frv_in_small_data_p
|
||
|
||
#undef TARGET_ASM_OUTPUT_MI_THUNK
|
||
#define TARGET_ASM_OUTPUT_MI_THUNK frv_asm_output_mi_thunk
|
||
#undef TARGET_ASM_CAN_OUTPUT_MI_THUNK
|
||
#define TARGET_ASM_CAN_OUTPUT_MI_THUNK default_can_output_mi_thunk_no_vcall
|
||
|
||
struct gcc_target targetm = TARGET_INITIALIZER;
|
||
|
||
/* Given a SYMBOL_REF, return true if it points to small data. */
|
||
|
||
static FRV_INLINE int
|
||
symbol_ref_small_data_p (x)
|
||
rtx x;
|
||
{
|
||
return SDATA_NAME_P (XSTR (x, 0));
|
||
}
|
||
|
||
/* Given a CONST, return true if the symbol_ref points to small data. */
|
||
|
||
static FRV_INLINE int
|
||
const_small_data_p (x)
|
||
rtx x;
|
||
{
|
||
rtx x0, x1;
|
||
|
||
if (GET_CODE (XEXP (x, 0)) != PLUS)
|
||
return FALSE;
|
||
|
||
x0 = XEXP (XEXP (x, 0), 0);
|
||
if (GET_CODE (x0) != SYMBOL_REF || !SDATA_NAME_P (XSTR (x0, 0)))
|
||
return FALSE;
|
||
|
||
x1 = XEXP (XEXP (x, 0), 1);
|
||
if (GET_CODE (x1) != CONST_INT
|
||
|| !IN_RANGE_P (INTVAL (x1), -2048, 2047))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* Given a PLUS, return true if this is a small data reference. */
|
||
|
||
static FRV_INLINE int
|
||
plus_small_data_p (op0, op1)
|
||
rtx op0;
|
||
rtx op1;
|
||
{
|
||
if (GET_MODE (op0) == SImode
|
||
&& GET_CODE (op0) == REG
|
||
&& REGNO (op0) == SDA_BASE_REG)
|
||
{
|
||
if (GET_CODE (op1) == SYMBOL_REF)
|
||
return symbol_ref_small_data_p (op1);
|
||
|
||
if (GET_CODE (op1) == CONST)
|
||
return const_small_data_p (op1);
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
static int
|
||
frv_default_flags_for_cpu ()
|
||
{
|
||
switch (frv_cpu_type)
|
||
{
|
||
case FRV_CPU_GENERIC:
|
||
return MASK_DEFAULT_FRV;
|
||
|
||
case FRV_CPU_FR500:
|
||
case FRV_CPU_TOMCAT:
|
||
return MASK_DEFAULT_FR500;
|
||
|
||
case FRV_CPU_FR400:
|
||
return MASK_DEFAULT_FR400;
|
||
|
||
case FRV_CPU_FR300:
|
||
case FRV_CPU_SIMPLE:
|
||
return MASK_DEFAULT_SIMPLE;
|
||
}
|
||
abort ();
|
||
}
|
||
|
||
/* Sometimes certain combinations of command options do not make
|
||
sense on a particular target machine. You can define a macro
|
||
`OVERRIDE_OPTIONS' to take account of this. This macro, if
|
||
defined, is executed once just after all the command options have
|
||
been parsed.
|
||
|
||
Don't use this macro to turn on various extra optimizations for
|
||
`-O'. That is what `OPTIMIZATION_OPTIONS' is for. */
|
||
|
||
void
|
||
frv_override_options ()
|
||
{
|
||
int regno, i;
|
||
|
||
/* Set the cpu type */
|
||
if (frv_cpu_string)
|
||
{
|
||
if (strcmp (frv_cpu_string, "simple") == 0)
|
||
frv_cpu_type = FRV_CPU_SIMPLE;
|
||
|
||
else if (strcmp (frv_cpu_string, "tomcat") == 0)
|
||
frv_cpu_type = FRV_CPU_TOMCAT;
|
||
|
||
else if (strncmp (frv_cpu_string, "fr", sizeof ("fr")-1) != 0)
|
||
error ("Unknown cpu: -mcpu=%s", frv_cpu_string);
|
||
|
||
else
|
||
{
|
||
const char *p = frv_cpu_string + sizeof ("fr") - 1;
|
||
if (strcmp (p, "500") == 0)
|
||
frv_cpu_type = FRV_CPU_FR500;
|
||
|
||
else if (strcmp (p, "400") == 0)
|
||
frv_cpu_type = FRV_CPU_FR400;
|
||
|
||
else if (strcmp (p, "300") == 0)
|
||
frv_cpu_type = FRV_CPU_FR300;
|
||
|
||
else if (strcmp (p, "v") == 0)
|
||
frv_cpu_type = FRV_CPU_GENERIC;
|
||
|
||
else
|
||
error ("Unknown cpu: -mcpu=%s", frv_cpu_string);
|
||
}
|
||
}
|
||
|
||
target_flags |= (frv_default_flags_for_cpu () & ~target_flags_explicit);
|
||
|
||
/* -mlibrary-pic sets -fPIC and -G0 and also suppresses warnings from the
|
||
linker about linking pic and non-pic code. */
|
||
if (TARGET_LIBPIC)
|
||
{
|
||
if (!flag_pic) /* -fPIC */
|
||
flag_pic = 2;
|
||
|
||
if (! g_switch_set) /* -G0 */
|
||
{
|
||
g_switch_set = 1;
|
||
g_switch_value = 0;
|
||
}
|
||
}
|
||
|
||
/* Both -fpic and -gdwarf want to use .previous and the assembler only keeps
|
||
one level. */
|
||
if (write_symbols == DWARF_DEBUG && flag_pic)
|
||
error ("-fpic and -gdwarf are incompatible (-fpic and -g/-gdwarf-2 are fine)");
|
||
|
||
/* Change the branch cost value */
|
||
if (frv_branch_cost_string)
|
||
frv_branch_cost_int = atoi (frv_branch_cost_string);
|
||
|
||
/* Change the # of insns to be converted to conditional execution */
|
||
if (frv_condexec_insns_str)
|
||
frv_condexec_insns = atoi (frv_condexec_insns_str);
|
||
|
||
/* Change # of temporary registers used to hold integer constants */
|
||
if (frv_condexec_temps_str)
|
||
frv_condexec_temps = atoi (frv_condexec_temps_str);
|
||
|
||
/* Change scheduling look ahead. */
|
||
if (frv_sched_lookahead_str)
|
||
frv_sched_lookahead = atoi (frv_sched_lookahead_str);
|
||
|
||
/* A C expression whose value is a register class containing hard
|
||
register REGNO. In general there is more than one such class;
|
||
choose a class which is "minimal", meaning that no smaller class
|
||
also contains the register. */
|
||
|
||
for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++)
|
||
{
|
||
enum reg_class class;
|
||
|
||
if (GPR_P (regno))
|
||
{
|
||
int gpr_reg = regno - GPR_FIRST;
|
||
if ((gpr_reg & 3) == 0)
|
||
class = QUAD_REGS;
|
||
|
||
else if ((gpr_reg & 1) == 0)
|
||
class = EVEN_REGS;
|
||
|
||
else
|
||
class = GPR_REGS;
|
||
}
|
||
|
||
else if (FPR_P (regno))
|
||
{
|
||
int fpr_reg = regno - GPR_FIRST;
|
||
if ((fpr_reg & 3) == 0)
|
||
class = QUAD_FPR_REGS;
|
||
|
||
else if ((fpr_reg & 1) == 0)
|
||
class = FEVEN_REGS;
|
||
|
||
else
|
||
class = FPR_REGS;
|
||
}
|
||
|
||
else if (regno == LR_REGNO)
|
||
class = LR_REG;
|
||
|
||
else if (regno == LCR_REGNO)
|
||
class = LCR_REG;
|
||
|
||
else if (ICC_P (regno))
|
||
class = ICC_REGS;
|
||
|
||
else if (FCC_P (regno))
|
||
class = FCC_REGS;
|
||
|
||
else if (ICR_P (regno))
|
||
class = ICR_REGS;
|
||
|
||
else if (FCR_P (regno))
|
||
class = FCR_REGS;
|
||
|
||
else if (ACC_P (regno))
|
||
{
|
||
int r = regno - ACC_FIRST;
|
||
if ((r & 3) == 0)
|
||
class = QUAD_ACC_REGS;
|
||
else if ((r & 1) == 0)
|
||
class = EVEN_ACC_REGS;
|
||
else
|
||
class = ACC_REGS;
|
||
}
|
||
|
||
else if (ACCG_P (regno))
|
||
class = ACCG_REGS;
|
||
|
||
else
|
||
class = NO_REGS;
|
||
|
||
regno_reg_class[regno] = class;
|
||
}
|
||
|
||
/* Check for small data option */
|
||
if (!g_switch_set)
|
||
g_switch_value = SDATA_DEFAULT_SIZE;
|
||
|
||
/* A C expression which defines the machine-dependent operand
|
||
constraint letters for register classes. If CHAR is such a
|
||
letter, the value should be the register class corresponding to
|
||
it. Otherwise, the value should be `NO_REGS'. The register
|
||
letter `r', corresponding to class `GENERAL_REGS', will not be
|
||
passed to this macro; you do not need to handle it.
|
||
|
||
The following letters are unavailable, due to being used as
|
||
constraints:
|
||
'0'..'9'
|
||
'<', '>'
|
||
'E', 'F', 'G', 'H'
|
||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P'
|
||
'Q', 'R', 'S', 'T', 'U'
|
||
'V', 'X'
|
||
'g', 'i', 'm', 'n', 'o', 'p', 'r', 's' */
|
||
|
||
for (i = 0; i < 256; i++)
|
||
reg_class_from_letter[i] = NO_REGS;
|
||
|
||
reg_class_from_letter['a'] = ACC_REGS;
|
||
reg_class_from_letter['b'] = EVEN_ACC_REGS;
|
||
reg_class_from_letter['c'] = CC_REGS;
|
||
reg_class_from_letter['d'] = GPR_REGS;
|
||
reg_class_from_letter['e'] = EVEN_REGS;
|
||
reg_class_from_letter['f'] = FPR_REGS;
|
||
reg_class_from_letter['h'] = FEVEN_REGS;
|
||
reg_class_from_letter['l'] = LR_REG;
|
||
reg_class_from_letter['q'] = QUAD_REGS;
|
||
reg_class_from_letter['t'] = ICC_REGS;
|
||
reg_class_from_letter['u'] = FCC_REGS;
|
||
reg_class_from_letter['v'] = ICR_REGS;
|
||
reg_class_from_letter['w'] = FCR_REGS;
|
||
reg_class_from_letter['x'] = QUAD_FPR_REGS;
|
||
reg_class_from_letter['y'] = LCR_REG;
|
||
reg_class_from_letter['z'] = SPR_REGS;
|
||
reg_class_from_letter['A'] = QUAD_ACC_REGS;
|
||
reg_class_from_letter['B'] = ACCG_REGS;
|
||
reg_class_from_letter['C'] = CR_REGS;
|
||
|
||
/* There is no single unaligned SI op for PIC code. Sometimes we
|
||
need to use ".4byte" and sometimes we need to use ".picptr".
|
||
See frv_assemble_integer for details. */
|
||
if (flag_pic)
|
||
targetm.asm_out.unaligned_op.si = 0;
|
||
|
||
init_machine_status = frv_init_machine_status;
|
||
}
|
||
|
||
|
||
/* Some machines may desire to change what optimizations are performed for
|
||
various optimization levels. This macro, if defined, is executed once just
|
||
after the optimization level is determined and before the remainder of the
|
||
command options have been parsed. Values set in this macro are used as the
|
||
default values for the other command line options.
|
||
|
||
LEVEL is the optimization level specified; 2 if `-O2' is specified, 1 if
|
||
`-O' is specified, and 0 if neither is specified.
|
||
|
||
SIZE is nonzero if `-Os' is specified, 0 otherwise.
|
||
|
||
You should not use this macro to change options that are not
|
||
machine-specific. These should uniformly selected by the same optimization
|
||
level on all supported machines. Use this macro to enable machbine-specific
|
||
optimizations.
|
||
|
||
*Do not examine `write_symbols' in this macro!* The debugging options are
|
||
*not supposed to alter the generated code. */
|
||
|
||
/* On the FRV, possibly disable VLIW packing which is done by the 2nd
|
||
scheduling pass at the current time. */
|
||
void
|
||
frv_optimization_options (level, size)
|
||
int level;
|
||
int size ATTRIBUTE_UNUSED;
|
||
{
|
||
if (level >= 2)
|
||
{
|
||
#ifdef DISABLE_SCHED2
|
||
flag_schedule_insns_after_reload = 0;
|
||
#endif
|
||
#ifdef ENABLE_RCSP
|
||
flag_rcsp = 1;
|
||
#endif
|
||
}
|
||
}
|
||
|
||
|
||
/* Return true if NAME (a STRING_CST node) begins with PREFIX. */
|
||
|
||
static int
|
||
frv_string_begins_with (name, prefix)
|
||
tree name;
|
||
const char *prefix;
|
||
{
|
||
int prefix_len = strlen (prefix);
|
||
|
||
/* Remember: NAME's length includes the null terminator. */
|
||
return (TREE_STRING_LENGTH (name) > prefix_len
|
||
&& strncmp (TREE_STRING_POINTER (name), prefix, prefix_len) == 0);
|
||
}
|
||
|
||
/* Encode section information of DECL, which is either a VAR_DECL,
|
||
FUNCTION_DECL, STRING_CST, CONSTRUCTOR, or ???.
|
||
|
||
For the FRV we want to record:
|
||
|
||
- whether the object lives in .sdata/.sbss.
|
||
objects living in .sdata/.sbss are prefixed with SDATA_FLAG_CHAR
|
||
|
||
*/
|
||
|
||
static void
|
||
frv_encode_section_info (decl, first)
|
||
tree decl;
|
||
int first;
|
||
{
|
||
if (! first)
|
||
return;
|
||
if (TREE_CODE (decl) == VAR_DECL)
|
||
{
|
||
int size = int_size_in_bytes (TREE_TYPE (decl));
|
||
tree section_name = DECL_SECTION_NAME (decl);
|
||
int is_small = 0;
|
||
|
||
/* Don't apply the -G flag to internal compiler structures. We
|
||
should leave such structures in the main data section, partly
|
||
for efficiency and partly because the size of some of them
|
||
(such as C++ typeinfos) is not known until later. */
|
||
if (!DECL_ARTIFICIAL (decl) && size > 0 && size <= g_switch_value)
|
||
is_small = 1;
|
||
|
||
/* If we already know which section the decl should be in, see if
|
||
it's a small data section. */
|
||
if (section_name)
|
||
{
|
||
if (TREE_CODE (section_name) == STRING_CST)
|
||
{
|
||
if (frv_string_begins_with (section_name, ".sdata"))
|
||
is_small = 1;
|
||
if (frv_string_begins_with (section_name, ".sbss"))
|
||
is_small = 1;
|
||
}
|
||
else
|
||
abort ();
|
||
}
|
||
|
||
if (is_small)
|
||
{
|
||
rtx sym_ref = XEXP (DECL_RTL (decl), 0);
|
||
char * str = xmalloc (2 + strlen (XSTR (sym_ref, 0)));
|
||
|
||
str[0] = SDATA_FLAG_CHAR;
|
||
strcpy (&str[1], XSTR (sym_ref, 0));
|
||
XSTR (sym_ref, 0) = str;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/* Zero or more C statements that may conditionally modify two variables
|
||
`fixed_regs' and `call_used_regs' (both of type `char []') after they have
|
||
been initialized from the two preceding macros.
|
||
|
||
This is necessary in case the fixed or call-clobbered registers depend on
|
||
target flags.
|
||
|
||
You need not define this macro if it has no work to do.
|
||
|
||
If the usage of an entire class of registers depends on the target flags,
|
||
you may indicate this to GCC by using this macro to modify `fixed_regs' and
|
||
`call_used_regs' to 1 for each of the registers in the classes which should
|
||
not be used by GCC. Also define the macro `REG_CLASS_FROM_LETTER' to return
|
||
`NO_REGS' if it is called with a letter for a class that shouldn't be used.
|
||
|
||
(However, if this class is not included in `GENERAL_REGS' and all of the
|
||
insn patterns whose constraints permit this class are controlled by target
|
||
switches, then GCC will automatically avoid using these registers when the
|
||
target switches are opposed to them.) */
|
||
|
||
void
|
||
frv_conditional_register_usage ()
|
||
{
|
||
int i;
|
||
|
||
for (i = GPR_FIRST + NUM_GPRS; i <= GPR_LAST; i++)
|
||
fixed_regs[i] = call_used_regs[i] = 1;
|
||
|
||
for (i = FPR_FIRST + NUM_FPRS; i <= FPR_LAST; i++)
|
||
fixed_regs[i] = call_used_regs[i] = 1;
|
||
|
||
for (i = ACC_FIRST + NUM_ACCS; i <= ACC_LAST; i++)
|
||
fixed_regs[i] = call_used_regs[i] = 1;
|
||
|
||
for (i = ACCG_FIRST + NUM_ACCS; i <= ACCG_LAST; i++)
|
||
fixed_regs[i] = call_used_regs[i] = 1;
|
||
|
||
/* Reserve the registers used for conditional execution. At present, we need
|
||
1 ICC and 1 ICR register. */
|
||
fixed_regs[ICC_TEMP] = call_used_regs[ICC_TEMP] = 1;
|
||
fixed_regs[ICR_TEMP] = call_used_regs[ICR_TEMP] = 1;
|
||
|
||
if (TARGET_FIXED_CC)
|
||
{
|
||
fixed_regs[ICC_FIRST] = call_used_regs[ICC_FIRST] = 1;
|
||
fixed_regs[FCC_FIRST] = call_used_regs[FCC_FIRST] = 1;
|
||
fixed_regs[ICR_FIRST] = call_used_regs[ICR_FIRST] = 1;
|
||
fixed_regs[FCR_FIRST] = call_used_regs[FCR_FIRST] = 1;
|
||
}
|
||
|
||
#if 0
|
||
/* If -fpic, SDA_BASE_REG is the PIC register. */
|
||
if (g_switch_value == 0 && !flag_pic)
|
||
fixed_regs[SDA_BASE_REG] = call_used_regs[SDA_BASE_REG] = 0;
|
||
|
||
if (!flag_pic)
|
||
fixed_regs[PIC_REGNO] = call_used_regs[PIC_REGNO] = 0;
|
||
#endif
|
||
}
|
||
|
||
|
||
/*
|
||
* Compute the stack frame layout
|
||
*
|
||
* Register setup:
|
||
* +---------------+-----------------------+-----------------------+
|
||
* |Register |type |caller-save/callee-save|
|
||
* +---------------+-----------------------+-----------------------+
|
||
* |GR0 |Zero register | - |
|
||
* |GR1 |Stack pointer(SP) | - |
|
||
* |GR2 |Frame pointer(FP) | - |
|
||
* |GR3 |Hidden parameter | caller save |
|
||
* |GR4-GR7 | - | caller save |
|
||
* |GR8-GR13 |Argument register | caller save |
|
||
* |GR14-GR15 | - | caller save |
|
||
* |GR16-GR31 | - | callee save |
|
||
* |GR32-GR47 | - | caller save |
|
||
* |GR48-GR63 | - | callee save |
|
||
* |FR0-FR15 | - | caller save |
|
||
* |FR16-FR31 | - | callee save |
|
||
* |FR32-FR47 | - | caller save |
|
||
* |FR48-FR63 | - | callee save |
|
||
* +---------------+-----------------------+-----------------------+
|
||
*
|
||
* Stack frame setup:
|
||
* Low
|
||
* SP-> |-----------------------------------|
|
||
* | Argument area |
|
||
* |-----------------------------------|
|
||
* | Register save area |
|
||
* |-----------------------------------|
|
||
* | Local variable save area |
|
||
* FP-> |-----------------------------------|
|
||
* | Old FP |
|
||
* |-----------------------------------|
|
||
* | Hidden parameter save area |
|
||
* |-----------------------------------|
|
||
* | Return address(LR) storage area |
|
||
* |-----------------------------------|
|
||
* | Padding for alignment |
|
||
* |-----------------------------------|
|
||
* | Register argument area |
|
||
* OLD SP-> |-----------------------------------|
|
||
* | Parameter area |
|
||
* |-----------------------------------|
|
||
* High
|
||
*
|
||
* Argument area/Parameter area:
|
||
*
|
||
* When a function is called, this area is used for argument transfer. When
|
||
* the argument is set up by the caller function, this area is referred to as
|
||
* the argument area. When the argument is referenced by the callee function,
|
||
* this area is referred to as the parameter area. The area is allocated when
|
||
* all arguments cannot be placed on the argument register at the time of
|
||
* argument transfer.
|
||
*
|
||
* Register save area:
|
||
*
|
||
* This is a register save area that must be guaranteed for the caller
|
||
* function. This area is not secured when the register save operation is not
|
||
* needed.
|
||
*
|
||
* Local variable save area:
|
||
*
|
||
* This is the area for local variables and temporary variables.
|
||
*
|
||
* Old FP:
|
||
*
|
||
* This area stores the FP value of the caller function.
|
||
*
|
||
* Hidden parameter save area:
|
||
*
|
||
* This area stores the start address of the return value storage
|
||
* area for a struct/union return function.
|
||
* When a struct/union is used as the return value, the caller
|
||
* function stores the return value storage area start address in
|
||
* register GR3 and passes it to the caller function.
|
||
* The callee function interprets the address stored in the GR3
|
||
* as the return value storage area start address.
|
||
* When register GR3 needs to be saved into memory, the callee
|
||
* function saves it in the hidden parameter save area. This
|
||
* area is not secured when the save operation is not needed.
|
||
*
|
||
* Return address(LR) storage area:
|
||
*
|
||
* This area saves the LR. The LR stores the address of a return to the caller
|
||
* function for the purpose of function calling.
|
||
*
|
||
* Argument register area:
|
||
*
|
||
* This area saves the argument register. This area is not secured when the
|
||
* save operation is not needed.
|
||
*
|
||
* Argument:
|
||
*
|
||
* Arguments, the count of which equals the count of argument registers (6
|
||
* words), are positioned in registers GR8 to GR13 and delivered to the callee
|
||
* function. When a struct/union return function is called, the return value
|
||
* area address is stored in register GR3. Arguments not placed in the
|
||
* argument registers will be stored in the stack argument area for transfer
|
||
* purposes. When an 8-byte type argument is to be delivered using registers,
|
||
* it is divided into two and placed in two registers for transfer. When
|
||
* argument registers must be saved to memory, the callee function secures an
|
||
* argument register save area in the stack. In this case, a continuous
|
||
* argument register save area must be established in the parameter area. The
|
||
* argument register save area must be allocated as needed to cover the size of
|
||
* the argument register to be saved. If the function has a variable count of
|
||
* arguments, it saves all argument registers in the argument register save
|
||
* area.
|
||
*
|
||
* Argument Extension Format:
|
||
*
|
||
* When an argument is to be stored in the stack, its type is converted to an
|
||
* extended type in accordance with the individual argument type. The argument
|
||
* is freed by the caller function after the return from the callee function is
|
||
* made.
|
||
*
|
||
* +-----------------------+---------------+------------------------+
|
||
* | Argument Type |Extended Type |Stack Storage Size(byte)|
|
||
* +-----------------------+---------------+------------------------+
|
||
* |char |int | 4 |
|
||
* |signed char |int | 4 |
|
||
* |unsigned char |int | 4 |
|
||
* |[signed] short int |int | 4 |
|
||
* |unsigned short int |int | 4 |
|
||
* |[signed] int |No extension | 4 |
|
||
* |unsigned int |No extension | 4 |
|
||
* |[signed] long int |No extension | 4 |
|
||
* |unsigned long int |No extension | 4 |
|
||
* |[signed] long long int |No extension | 8 |
|
||
* |unsigned long long int |No extension | 8 |
|
||
* |float |double | 8 |
|
||
* |double |No extension | 8 |
|
||
* |long double |No extension | 8 |
|
||
* |pointer |No extension | 4 |
|
||
* |struct/union |- | 4 (*1) |
|
||
* +-----------------------+---------------+------------------------+
|
||
*
|
||
* When a struct/union is to be delivered as an argument, the caller copies it
|
||
* to the local variable area and delivers the address of that area.
|
||
*
|
||
* Return Value:
|
||
*
|
||
* +-------------------------------+----------------------+
|
||
* |Return Value Type |Return Value Interface|
|
||
* +-------------------------------+----------------------+
|
||
* |void |None |
|
||
* |[signed|unsigned] char |GR8 |
|
||
* |[signed|unsigned] short int |GR8 |
|
||
* |[signed|unsigned] int |GR8 |
|
||
* |[signed|unsigned] long int |GR8 |
|
||
* |pointer |GR8 |
|
||
* |[signed|unsigned] long long int|GR8 & GR9 |
|
||
* |float |GR8 |
|
||
* |double |GR8 & GR9 |
|
||
* |long double |GR8 & GR9 |
|
||
* |struct/union |(*1) |
|
||
* +-------------------------------+----------------------+
|
||
*
|
||
* When a struct/union is used as the return value, the caller function stores
|
||
* the start address of the return value storage area into GR3 and then passes
|
||
* it to the callee function. The callee function interprets GR3 as the start
|
||
* address of the return value storage area. When this address needs to be
|
||
* saved in memory, the callee function secures the hidden parameter save area
|
||
* and saves the address in that area.
|
||
*/
|
||
|
||
frv_stack_t *
|
||
frv_stack_info ()
|
||
{
|
||
static frv_stack_t info, zero_info;
|
||
frv_stack_t *info_ptr = &info;
|
||
tree fndecl = current_function_decl;
|
||
int varargs_p = 0;
|
||
tree cur_arg;
|
||
tree next_arg;
|
||
int range;
|
||
int alignment;
|
||
int offset;
|
||
|
||
/* If we've already calculated the values and reload is complete, just return now */
|
||
if (frv_stack_cache)
|
||
return frv_stack_cache;
|
||
|
||
/* Zero all fields */
|
||
info = zero_info;
|
||
|
||
/* Set up the register range information */
|
||
info_ptr->regs[STACK_REGS_GPR].name = "gpr";
|
||
info_ptr->regs[STACK_REGS_GPR].first = LAST_ARG_REGNUM + 1;
|
||
info_ptr->regs[STACK_REGS_GPR].last = GPR_LAST;
|
||
info_ptr->regs[STACK_REGS_GPR].dword_p = TRUE;
|
||
|
||
info_ptr->regs[STACK_REGS_FPR].name = "fpr";
|
||
info_ptr->regs[STACK_REGS_FPR].first = FPR_FIRST;
|
||
info_ptr->regs[STACK_REGS_FPR].last = FPR_LAST;
|
||
info_ptr->regs[STACK_REGS_FPR].dword_p = TRUE;
|
||
|
||
info_ptr->regs[STACK_REGS_LR].name = "lr";
|
||
info_ptr->regs[STACK_REGS_LR].first = LR_REGNO;
|
||
info_ptr->regs[STACK_REGS_LR].last = LR_REGNO;
|
||
info_ptr->regs[STACK_REGS_LR].special_p = 1;
|
||
|
||
info_ptr->regs[STACK_REGS_CC].name = "cc";
|
||
info_ptr->regs[STACK_REGS_CC].first = CC_FIRST;
|
||
info_ptr->regs[STACK_REGS_CC].last = CC_LAST;
|
||
info_ptr->regs[STACK_REGS_CC].field_p = TRUE;
|
||
|
||
info_ptr->regs[STACK_REGS_LCR].name = "lcr";
|
||
info_ptr->regs[STACK_REGS_LCR].first = LCR_REGNO;
|
||
info_ptr->regs[STACK_REGS_LCR].last = LCR_REGNO;
|
||
|
||
info_ptr->regs[STACK_REGS_STDARG].name = "stdarg";
|
||
info_ptr->regs[STACK_REGS_STDARG].first = FIRST_ARG_REGNUM;
|
||
info_ptr->regs[STACK_REGS_STDARG].last = LAST_ARG_REGNUM;
|
||
info_ptr->regs[STACK_REGS_STDARG].dword_p = 1;
|
||
info_ptr->regs[STACK_REGS_STDARG].special_p = 1;
|
||
|
||
info_ptr->regs[STACK_REGS_STRUCT].name = "struct";
|
||
info_ptr->regs[STACK_REGS_STRUCT].first = STRUCT_VALUE_REGNUM;
|
||
info_ptr->regs[STACK_REGS_STRUCT].last = STRUCT_VALUE_REGNUM;
|
||
info_ptr->regs[STACK_REGS_STRUCT].special_p = 1;
|
||
|
||
info_ptr->regs[STACK_REGS_FP].name = "fp";
|
||
info_ptr->regs[STACK_REGS_FP].first = FRAME_POINTER_REGNUM;
|
||
info_ptr->regs[STACK_REGS_FP].last = FRAME_POINTER_REGNUM;
|
||
info_ptr->regs[STACK_REGS_FP].special_p = 1;
|
||
|
||
/* Determine if this is a stdarg function. If so, allocate space to store
|
||
the 6 arguments. */
|
||
if (cfun->stdarg)
|
||
varargs_p = 1;
|
||
|
||
else
|
||
{
|
||
/* Find the last argument, and see if it is __builtin_va_alist. */
|
||
for (cur_arg = DECL_ARGUMENTS (fndecl); cur_arg != (tree)0; cur_arg = next_arg)
|
||
{
|
||
next_arg = TREE_CHAIN (cur_arg);
|
||
if (next_arg == (tree)0)
|
||
{
|
||
if (DECL_NAME (cur_arg)
|
||
&& !strcmp (IDENTIFIER_POINTER (DECL_NAME (cur_arg)), "__builtin_va_alist"))
|
||
varargs_p = 1;
|
||
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Iterate over all of the register ranges */
|
||
for (range = 0; range < STACK_REGS_MAX; range++)
|
||
{
|
||
frv_stack_regs_t *reg_ptr = &(info_ptr->regs[range]);
|
||
int first = reg_ptr->first;
|
||
int last = reg_ptr->last;
|
||
int size_1word = 0;
|
||
int size_2words = 0;
|
||
int regno;
|
||
|
||
/* Calculate which registers need to be saved & save area size */
|
||
switch (range)
|
||
{
|
||
default:
|
||
for (regno = first; regno <= last; regno++)
|
||
{
|
||
if ((regs_ever_live[regno] && !call_used_regs[regno])
|
||
|| (current_function_calls_eh_return
|
||
&& (regno >= FIRST_EH_REGNUM && regno <= LAST_EH_REGNUM))
|
||
|| (flag_pic && cfun->uses_pic_offset_table && regno == PIC_REGNO))
|
||
{
|
||
info_ptr->save_p[regno] = REG_SAVE_1WORD;
|
||
size_1word += UNITS_PER_WORD;
|
||
}
|
||
}
|
||
break;
|
||
|
||
/* Calculate whether we need to create a frame after everything else
|
||
has been processed. */
|
||
case STACK_REGS_FP:
|
||
break;
|
||
|
||
case STACK_REGS_LR:
|
||
if (regs_ever_live[LR_REGNO]
|
||
|| profile_flag
|
||
|| frame_pointer_needed
|
||
|| (flag_pic && cfun->uses_pic_offset_table))
|
||
{
|
||
info_ptr->save_p[LR_REGNO] = REG_SAVE_1WORD;
|
||
size_1word += UNITS_PER_WORD;
|
||
}
|
||
break;
|
||
|
||
case STACK_REGS_STDARG:
|
||
if (varargs_p)
|
||
{
|
||
/* If this is a stdarg function with an non varardic argument split
|
||
between registers and the stack, adjust the saved registers
|
||
downward */
|
||
last -= (ADDR_ALIGN (cfun->pretend_args_size, UNITS_PER_WORD)
|
||
/ UNITS_PER_WORD);
|
||
|
||
for (regno = first; regno <= last; regno++)
|
||
{
|
||
info_ptr->save_p[regno] = REG_SAVE_1WORD;
|
||
size_1word += UNITS_PER_WORD;
|
||
}
|
||
|
||
info_ptr->stdarg_size = size_1word;
|
||
}
|
||
break;
|
||
|
||
case STACK_REGS_STRUCT:
|
||
if (cfun->returns_struct)
|
||
{
|
||
info_ptr->save_p[STRUCT_VALUE_REGNUM] = REG_SAVE_1WORD;
|
||
size_1word += UNITS_PER_WORD;
|
||
}
|
||
break;
|
||
}
|
||
|
||
|
||
if (size_1word)
|
||
{
|
||
/* If this is a field, it only takes one word */
|
||
if (reg_ptr->field_p)
|
||
size_1word = UNITS_PER_WORD;
|
||
|
||
/* Determine which register pairs can be saved together */
|
||
else if (reg_ptr->dword_p && TARGET_DWORD)
|
||
{
|
||
for (regno = first; regno < last; regno += 2)
|
||
{
|
||
if (info_ptr->save_p[regno] && info_ptr->save_p[regno+1])
|
||
{
|
||
size_2words += 2 * UNITS_PER_WORD;
|
||
size_1word -= 2 * UNITS_PER_WORD;
|
||
info_ptr->save_p[regno] = REG_SAVE_2WORDS;
|
||
info_ptr->save_p[regno+1] = REG_SAVE_NO_SAVE;
|
||
}
|
||
}
|
||
}
|
||
|
||
reg_ptr->size_1word = size_1word;
|
||
reg_ptr->size_2words = size_2words;
|
||
|
||
if (! reg_ptr->special_p)
|
||
{
|
||
info_ptr->regs_size_1word += size_1word;
|
||
info_ptr->regs_size_2words += size_2words;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Set up the sizes of each each field in the frame body, making the sizes
|
||
of each be divisible by the size of a dword if dword operations might
|
||
be used, or the size of a word otherwise. */
|
||
alignment = (TARGET_DWORD? 2 * UNITS_PER_WORD : UNITS_PER_WORD);
|
||
|
||
info_ptr->parameter_size = ADDR_ALIGN (cfun->outgoing_args_size, alignment);
|
||
info_ptr->regs_size = ADDR_ALIGN (info_ptr->regs_size_2words
|
||
+ info_ptr->regs_size_1word,
|
||
alignment);
|
||
info_ptr->vars_size = ADDR_ALIGN (get_frame_size (), alignment);
|
||
|
||
info_ptr->pretend_size = cfun->pretend_args_size;
|
||
|
||
/* Work out the size of the frame, excluding the header. Both the frame
|
||
body and register parameter area will be dword-aligned. */
|
||
info_ptr->total_size
|
||
= (ADDR_ALIGN (info_ptr->parameter_size
|
||
+ info_ptr->regs_size
|
||
+ info_ptr->vars_size,
|
||
2 * UNITS_PER_WORD)
|
||
+ ADDR_ALIGN (info_ptr->pretend_size
|
||
+ info_ptr->stdarg_size,
|
||
2 * UNITS_PER_WORD));
|
||
|
||
/* See if we need to create a frame at all, if so add header area. */
|
||
if (info_ptr->total_size > 0
|
||
|| info_ptr->regs[STACK_REGS_LR].size_1word > 0
|
||
|| info_ptr->regs[STACK_REGS_STRUCT].size_1word > 0)
|
||
{
|
||
offset = info_ptr->parameter_size;
|
||
info_ptr->header_size = 4 * UNITS_PER_WORD;
|
||
info_ptr->total_size += 4 * UNITS_PER_WORD;
|
||
|
||
/* Calculate the offsets to save normal register pairs */
|
||
for (range = 0; range < STACK_REGS_MAX; range++)
|
||
{
|
||
frv_stack_regs_t *reg_ptr = &(info_ptr->regs[range]);
|
||
if (! reg_ptr->special_p)
|
||
{
|
||
int first = reg_ptr->first;
|
||
int last = reg_ptr->last;
|
||
int regno;
|
||
|
||
for (regno = first; regno <= last; regno++)
|
||
if (info_ptr->save_p[regno] == REG_SAVE_2WORDS
|
||
&& regno != FRAME_POINTER_REGNUM
|
||
&& (regno < FIRST_ARG_REGNUM
|
||
|| regno > LAST_ARG_REGNUM))
|
||
{
|
||
info_ptr->reg_offset[regno] = offset;
|
||
offset += 2 * UNITS_PER_WORD;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Calculate the offsets to save normal single registers */
|
||
for (range = 0; range < STACK_REGS_MAX; range++)
|
||
{
|
||
frv_stack_regs_t *reg_ptr = &(info_ptr->regs[range]);
|
||
if (! reg_ptr->special_p)
|
||
{
|
||
int first = reg_ptr->first;
|
||
int last = reg_ptr->last;
|
||
int regno;
|
||
|
||
for (regno = first; regno <= last; regno++)
|
||
if (info_ptr->save_p[regno] == REG_SAVE_1WORD
|
||
&& regno != FRAME_POINTER_REGNUM
|
||
&& (regno < FIRST_ARG_REGNUM
|
||
|| regno > LAST_ARG_REGNUM))
|
||
{
|
||
info_ptr->reg_offset[regno] = offset;
|
||
offset += UNITS_PER_WORD;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Calculate the offset to save the local variables at. */
|
||
offset = ADDR_ALIGN (offset, alignment);
|
||
if (info_ptr->vars_size)
|
||
{
|
||
info_ptr->vars_offset = offset;
|
||
offset += info_ptr->vars_size;
|
||
}
|
||
|
||
/* Align header to a dword-boundary. */
|
||
offset = ADDR_ALIGN (offset, 2 * UNITS_PER_WORD);
|
||
|
||
/* Calculate the offsets in the fixed frame. */
|
||
info_ptr->save_p[FRAME_POINTER_REGNUM] = REG_SAVE_1WORD;
|
||
info_ptr->reg_offset[FRAME_POINTER_REGNUM] = offset;
|
||
info_ptr->regs[STACK_REGS_FP].size_1word = UNITS_PER_WORD;
|
||
|
||
info_ptr->save_p[LR_REGNO] = REG_SAVE_1WORD;
|
||
info_ptr->reg_offset[LR_REGNO] = offset + 2*UNITS_PER_WORD;
|
||
info_ptr->regs[STACK_REGS_LR].size_1word = UNITS_PER_WORD;
|
||
|
||
if (cfun->returns_struct)
|
||
{
|
||
info_ptr->save_p[STRUCT_VALUE_REGNUM] = REG_SAVE_1WORD;
|
||
info_ptr->reg_offset[STRUCT_VALUE_REGNUM] = offset + UNITS_PER_WORD;
|
||
info_ptr->regs[STACK_REGS_STRUCT].size_1word = UNITS_PER_WORD;
|
||
}
|
||
|
||
/* Calculate the offsets to store the arguments passed in registers
|
||
for stdarg functions. The register pairs are first and the single
|
||
register if any is last. The register save area starts on a
|
||
dword-boundary. */
|
||
if (info_ptr->stdarg_size)
|
||
{
|
||
int first = info_ptr->regs[STACK_REGS_STDARG].first;
|
||
int last = info_ptr->regs[STACK_REGS_STDARG].last;
|
||
int regno;
|
||
|
||
/* Skip the header. */
|
||
offset += 4 * UNITS_PER_WORD;
|
||
for (regno = first; regno <= last; regno++)
|
||
{
|
||
if (info_ptr->save_p[regno] == REG_SAVE_2WORDS)
|
||
{
|
||
info_ptr->reg_offset[regno] = offset;
|
||
offset += 2 * UNITS_PER_WORD;
|
||
}
|
||
else if (info_ptr->save_p[regno] == REG_SAVE_1WORD)
|
||
{
|
||
info_ptr->reg_offset[regno] = offset;
|
||
offset += UNITS_PER_WORD;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (reload_completed)
|
||
frv_stack_cache = info_ptr;
|
||
|
||
return info_ptr;
|
||
}
|
||
|
||
|
||
/* Print the information about the frv stack offsets, etc. when debugging. */
|
||
|
||
void
|
||
frv_debug_stack (info)
|
||
frv_stack_t *info;
|
||
{
|
||
int range;
|
||
|
||
if (!info)
|
||
info = frv_stack_info ();
|
||
|
||
fprintf (stderr, "\nStack information for function %s:\n",
|
||
((current_function_decl && DECL_NAME (current_function_decl))
|
||
? IDENTIFIER_POINTER (DECL_NAME (current_function_decl))
|
||
: "<unknown>"));
|
||
|
||
fprintf (stderr, "\ttotal_size\t= %6d\n", info->total_size);
|
||
fprintf (stderr, "\tvars_size\t= %6d\n", info->vars_size);
|
||
fprintf (stderr, "\tparam_size\t= %6d\n", info->parameter_size);
|
||
fprintf (stderr, "\tregs_size\t= %6d, 1w = %3d, 2w = %3d\n",
|
||
info->regs_size, info->regs_size_1word, info->regs_size_2words);
|
||
|
||
fprintf (stderr, "\theader_size\t= %6d\n", info->header_size);
|
||
fprintf (stderr, "\tpretend_size\t= %6d\n", info->pretend_size);
|
||
fprintf (stderr, "\tvars_offset\t= %6d\n", info->vars_offset);
|
||
fprintf (stderr, "\tregs_offset\t= %6d\n", info->regs_offset);
|
||
|
||
for (range = 0; range < STACK_REGS_MAX; range++)
|
||
{
|
||
frv_stack_regs_t *regs = &(info->regs[range]);
|
||
if ((regs->size_1word + regs->size_2words) > 0)
|
||
{
|
||
int first = regs->first;
|
||
int last = regs->last;
|
||
int regno;
|
||
|
||
fprintf (stderr, "\t%s\tsize\t= %6d, 1w = %3d, 2w = %3d, save =",
|
||
regs->name, regs->size_1word + regs->size_2words,
|
||
regs->size_1word, regs->size_2words);
|
||
|
||
for (regno = first; regno <= last; regno++)
|
||
{
|
||
if (info->save_p[regno] == REG_SAVE_1WORD)
|
||
fprintf (stderr, " %s (%d)", reg_names[regno],
|
||
info->reg_offset[regno]);
|
||
|
||
else if (info->save_p[regno] == REG_SAVE_2WORDS)
|
||
fprintf (stderr, " %s-%s (%d)", reg_names[regno],
|
||
reg_names[regno+1], info->reg_offset[regno]);
|
||
}
|
||
|
||
fputc ('\n', stderr);
|
||
}
|
||
}
|
||
|
||
fflush (stderr);
|
||
}
|
||
|
||
|
||
|
||
|
||
/* The following variable value is TRUE if the next output insn should
|
||
finish cpu cycle. In order words the insn will have packing bit
|
||
(which means absence of asm code suffix `.p' on assembler. */
|
||
|
||
static int frv_insn_packing_flag;
|
||
|
||
/* True if the current function contains a far jump. */
|
||
|
||
static int
|
||
frv_function_contains_far_jump ()
|
||
{
|
||
rtx insn = get_insns ();
|
||
while (insn != NULL
|
||
&& !(GET_CODE (insn) == JUMP_INSN
|
||
/* Ignore tablejump patterns. */
|
||
&& GET_CODE (PATTERN (insn)) != ADDR_VEC
|
||
&& GET_CODE (PATTERN (insn)) != ADDR_DIFF_VEC
|
||
&& get_attr_far_jump (insn) == FAR_JUMP_YES))
|
||
insn = NEXT_INSN (insn);
|
||
return (insn != NULL);
|
||
}
|
||
|
||
/* For the FRV, this function makes sure that a function with far jumps
|
||
will return correctly. It also does the VLIW packing. */
|
||
|
||
static void
|
||
frv_function_prologue (file, size)
|
||
FILE *file;
|
||
HOST_WIDE_INT size ATTRIBUTE_UNUSED;
|
||
{
|
||
/* If no frame was created, check whether the function uses a call
|
||
instruction to implement a far jump. If so, save the link in gr3 and
|
||
replace all returns to LR with returns to GR3. GR3 is used because it
|
||
is call-clobbered, because is not available to the register allocator,
|
||
and because all functions that take a hidden argument pointer will have
|
||
a stack frame. */
|
||
if (frv_stack_info ()->total_size == 0 && frv_function_contains_far_jump ())
|
||
{
|
||
rtx insn;
|
||
|
||
/* Just to check that the above comment is true. */
|
||
if (regs_ever_live[GPR_FIRST + 3])
|
||
abort ();
|
||
|
||
/* Generate the instruction that saves the link register. */
|
||
fprintf (file, "\tmovsg lr,gr3\n");
|
||
|
||
/* Replace the LR with GR3 in *return_internal patterns. The insn
|
||
will now return using jmpl @(gr3,0) rather than bralr. We cannot
|
||
simply emit a different assembly directive because bralr and jmpl
|
||
execute in different units. */
|
||
for (insn = get_insns(); insn != NULL; insn = NEXT_INSN (insn))
|
||
if (GET_CODE (insn) == JUMP_INSN)
|
||
{
|
||
rtx pattern = PATTERN (insn);
|
||
if (GET_CODE (pattern) == PARALLEL
|
||
&& XVECLEN (pattern, 0) >= 2
|
||
&& GET_CODE (XVECEXP (pattern, 0, 0)) == RETURN
|
||
&& GET_CODE (XVECEXP (pattern, 0, 1)) == USE)
|
||
{
|
||
rtx address = XEXP (XVECEXP (pattern, 0, 1), 0);
|
||
if (GET_CODE (address) == REG && REGNO (address) == LR_REGNO)
|
||
REGNO (address) = GPR_FIRST + 3;
|
||
}
|
||
}
|
||
}
|
||
|
||
frv_pack_insns ();
|
||
frv_insn_packing_flag = TRUE;
|
||
}
|
||
|
||
|
||
/* Return the next available temporary register in a given class. */
|
||
|
||
static rtx
|
||
frv_alloc_temp_reg (info, class, mode, mark_as_used, no_abort)
|
||
frv_tmp_reg_t *info; /* which registers are available */
|
||
enum reg_class class; /* register class desired */
|
||
enum machine_mode mode; /* mode to allocate register with */
|
||
int mark_as_used; /* register not available after allocation */
|
||
int no_abort; /* return NULL instead of aborting */
|
||
{
|
||
int regno = info->next_reg[ (int)class ];
|
||
int orig_regno = regno;
|
||
HARD_REG_SET *reg_in_class = ®_class_contents[ (int)class ];
|
||
int i, nr;
|
||
|
||
for (;;)
|
||
{
|
||
if (TEST_HARD_REG_BIT (*reg_in_class, regno)
|
||
&& TEST_HARD_REG_BIT (info->regs, regno))
|
||
break;
|
||
|
||
if (++regno >= FIRST_PSEUDO_REGISTER)
|
||
regno = 0;
|
||
if (regno == orig_regno)
|
||
{
|
||
if (no_abort)
|
||
return NULL_RTX;
|
||
else
|
||
abort ();
|
||
}
|
||
}
|
||
|
||
nr = HARD_REGNO_NREGS (regno, mode);
|
||
info->next_reg[ (int)class ] = regno + nr;
|
||
|
||
if (mark_as_used)
|
||
for (i = 0; i < nr; i++)
|
||
CLEAR_HARD_REG_BIT (info->regs, regno+i);
|
||
|
||
return gen_rtx_REG (mode, regno);
|
||
}
|
||
|
||
|
||
/* Return an rtx with the value OFFSET, which will either be a register or a
|
||
signed 12-bit integer. It can be used as the second operand in an "add"
|
||
instruction, or as the index in a load or store.
|
||
|
||
The function returns a constant rtx if OFFSET is small enough, otherwise
|
||
it loads the constant into register OFFSET_REGNO and returns that. */
|
||
static rtx
|
||
frv_frame_offset_rtx (offset)
|
||
int offset;
|
||
{
|
||
rtx offset_rtx = GEN_INT (offset);
|
||
if (IN_RANGE_P (offset, -2048, 2047))
|
||
return offset_rtx;
|
||
else
|
||
{
|
||
rtx reg_rtx = gen_rtx_REG (SImode, OFFSET_REGNO);
|
||
if (IN_RANGE_P (offset, -32768, 32767))
|
||
emit_insn (gen_movsi (reg_rtx, offset_rtx));
|
||
else
|
||
{
|
||
emit_insn (gen_movsi_high (reg_rtx, offset_rtx));
|
||
emit_insn (gen_movsi_lo_sum (reg_rtx, offset_rtx));
|
||
}
|
||
return reg_rtx;
|
||
}
|
||
}
|
||
|
||
/* Generate (mem:MODE (plus:Pmode BASE (frv_frame_offset OFFSET)))). The
|
||
prologue and epilogue uses such expressions to access the stack. */
|
||
static rtx
|
||
frv_frame_mem (mode, base, offset)
|
||
enum machine_mode mode;
|
||
rtx base;
|
||
int offset;
|
||
{
|
||
return gen_rtx_MEM (mode, gen_rtx_PLUS (Pmode,
|
||
base,
|
||
frv_frame_offset_rtx (offset)));
|
||
}
|
||
|
||
/* Generate a frame-related expression:
|
||
|
||
(set REG (mem (plus (sp) (const_int OFFSET)))).
|
||
|
||
Such expressions are used in FRAME_RELATED_EXPR notes for more complex
|
||
instructions. Marking the expressions as frame-related is superfluous if
|
||
the note contains just a single set. But if the note contains a PARALLEL
|
||
or SEQUENCE that has several sets, each set must be individually marked
|
||
as frame-related. */
|
||
static rtx
|
||
frv_dwarf_store (reg, offset)
|
||
rtx reg;
|
||
int offset;
|
||
{
|
||
rtx set = gen_rtx_SET (VOIDmode,
|
||
gen_rtx_MEM (GET_MODE (reg),
|
||
plus_constant (stack_pointer_rtx,
|
||
offset)),
|
||
reg);
|
||
RTX_FRAME_RELATED_P (set) = 1;
|
||
return set;
|
||
}
|
||
|
||
/* Emit a frame-related instruction whose pattern is PATTERN. The
|
||
instruction is the last in a sequence that cumulatively performs the
|
||
operation described by DWARF_PATTERN. The instruction is marked as
|
||
frame-related and has a REG_FRAME_RELATED_EXPR note containing
|
||
DWARF_PATTERN. */
|
||
static void
|
||
frv_frame_insn (pattern, dwarf_pattern)
|
||
rtx pattern;
|
||
rtx dwarf_pattern;
|
||
{
|
||
rtx insn = emit_insn (pattern);
|
||
RTX_FRAME_RELATED_P (insn) = 1;
|
||
REG_NOTES (insn) = alloc_EXPR_LIST (REG_FRAME_RELATED_EXPR,
|
||
dwarf_pattern,
|
||
REG_NOTES (insn));
|
||
}
|
||
|
||
/* Emit instructions that transfer REG to or from the memory location (sp +
|
||
STACK_OFFSET). The register is stored in memory if ACCESSOR->OP is
|
||
FRV_STORE and loaded if it is FRV_LOAD. Only the prologue uses this
|
||
function to store registers and only the epilogue uses it to load them.
|
||
|
||
The caller sets up ACCESSOR so that BASE is equal to (sp + BASE_OFFSET).
|
||
The generated instruction will use BASE as its base register. BASE may
|
||
simply be the stack pointer, but if several accesses are being made to a
|
||
region far away from the stack pointer, it may be more efficient to set
|
||
up a temporary instead.
|
||
|
||
Store instructions will be frame-related and will be annotated with the
|
||
overall effect of the store. Load instructions will be followed by a
|
||
(use) to prevent later optimizations from zapping them.
|
||
|
||
The function takes care of the moves to and from SPRs, using TEMP_REGNO
|
||
as a temporary in such cases. */
|
||
static void
|
||
frv_frame_access (accessor, reg, stack_offset)
|
||
frv_frame_accessor_t *accessor;
|
||
rtx reg;
|
||
int stack_offset;
|
||
{
|
||
enum machine_mode mode = GET_MODE (reg);
|
||
rtx mem = frv_frame_mem (mode,
|
||
accessor->base,
|
||
stack_offset - accessor->base_offset);
|
||
|
||
if (accessor->op == FRV_LOAD)
|
||
{
|
||
if (SPR_P (REGNO (reg)))
|
||
{
|
||
rtx temp = gen_rtx_REG (mode, TEMP_REGNO);
|
||
emit_insn (gen_rtx_SET (VOIDmode, temp, mem));
|
||
emit_insn (gen_rtx_SET (VOIDmode, reg, temp));
|
||
}
|
||
else
|
||
emit_insn (gen_rtx_SET (VOIDmode, reg, mem));
|
||
emit_insn (gen_rtx_USE (VOIDmode, reg));
|
||
}
|
||
else
|
||
{
|
||
if (SPR_P (REGNO (reg)))
|
||
{
|
||
rtx temp = gen_rtx_REG (mode, TEMP_REGNO);
|
||
emit_insn (gen_rtx_SET (VOIDmode, temp, reg));
|
||
frv_frame_insn (gen_rtx_SET (Pmode, mem, temp),
|
||
frv_dwarf_store (reg, stack_offset));
|
||
}
|
||
else if (GET_MODE (reg) == DImode)
|
||
{
|
||
/* For DImode saves, the dwarf2 version needs to be a SEQUENCE
|
||
with a separate save for each register. */
|
||
rtx reg1 = gen_rtx_REG (SImode, REGNO (reg));
|
||
rtx reg2 = gen_rtx_REG (SImode, REGNO (reg) + 1);
|
||
rtx set1 = frv_dwarf_store (reg1, stack_offset);
|
||
rtx set2 = frv_dwarf_store (reg2, stack_offset + 4);
|
||
frv_frame_insn (gen_rtx_SET (Pmode, mem, reg),
|
||
gen_rtx_PARALLEL (VOIDmode,
|
||
gen_rtvec (2, set1, set2)));
|
||
}
|
||
else
|
||
frv_frame_insn (gen_rtx_SET (Pmode, mem, reg),
|
||
frv_dwarf_store (reg, stack_offset));
|
||
}
|
||
}
|
||
|
||
/* A function that uses frv_frame_access to transfer a group of registers to
|
||
or from the stack. ACCESSOR is passed directly to frv_frame_access, INFO
|
||
is the stack information generated by frv_stack_info, and REG_SET is the
|
||
number of the register set to transfer. */
|
||
static void
|
||
frv_frame_access_multi (accessor, info, reg_set)
|
||
frv_frame_accessor_t *accessor;
|
||
frv_stack_t *info;
|
||
int reg_set;
|
||
{
|
||
frv_stack_regs_t *regs_info;
|
||
int regno;
|
||
|
||
regs_info = &info->regs[reg_set];
|
||
for (regno = regs_info->first; regno <= regs_info->last; regno++)
|
||
if (info->save_p[regno])
|
||
frv_frame_access (accessor,
|
||
info->save_p[regno] == REG_SAVE_2WORDS
|
||
? gen_rtx_REG (DImode, regno)
|
||
: gen_rtx_REG (SImode, regno),
|
||
info->reg_offset[regno]);
|
||
}
|
||
|
||
/* Save or restore callee-saved registers that are kept outside the frame
|
||
header. The function saves the registers if OP is FRV_STORE and restores
|
||
them if OP is FRV_LOAD. INFO is the stack information generated by
|
||
frv_stack_info. */
|
||
static void
|
||
frv_frame_access_standard_regs (op, info)
|
||
enum frv_stack_op op;
|
||
frv_stack_t *info;
|
||
{
|
||
frv_frame_accessor_t accessor;
|
||
|
||
accessor.op = op;
|
||
accessor.base = stack_pointer_rtx;
|
||
accessor.base_offset = 0;
|
||
frv_frame_access_multi (&accessor, info, STACK_REGS_GPR);
|
||
frv_frame_access_multi (&accessor, info, STACK_REGS_FPR);
|
||
frv_frame_access_multi (&accessor, info, STACK_REGS_LCR);
|
||
}
|
||
|
||
|
||
/* Called after register allocation to add any instructions needed for the
|
||
prologue. Using a prologue insn is favored compared to putting all of the
|
||
instructions in the FUNCTION_PROLOGUE macro, since it allows the scheduler
|
||
to intermix instructions with the saves of the caller saved registers. In
|
||
some cases, it might be necessary to emit a barrier instruction as the last
|
||
insn to prevent such scheduling.
|
||
|
||
Also any insns generated here should have RTX_FRAME_RELATED_P(insn) = 1
|
||
so that the debug info generation code can handle them properly. */
|
||
void
|
||
frv_expand_prologue ()
|
||
{
|
||
frv_stack_t *info = frv_stack_info ();
|
||
rtx sp = stack_pointer_rtx;
|
||
rtx fp = frame_pointer_rtx;
|
||
frv_frame_accessor_t accessor;
|
||
|
||
if (TARGET_DEBUG_STACK)
|
||
frv_debug_stack (info);
|
||
|
||
if (info->total_size == 0)
|
||
return;
|
||
|
||
/* We're interested in three areas of the frame here:
|
||
|
||
A: the register save area
|
||
B: the old FP
|
||
C: the header after B
|
||
|
||
If the frame pointer isn't used, we'll have to set up A, B and C
|
||
using the stack pointer. If the frame pointer is used, we'll access
|
||
them as follows:
|
||
|
||
A: set up using sp
|
||
B: set up using sp or a temporary (see below)
|
||
C: set up using fp
|
||
|
||
We set up B using the stack pointer if the frame is small enough.
|
||
Otherwise, it's more efficient to copy the old stack pointer into a
|
||
temporary and use that.
|
||
|
||
Note that it's important to make sure the prologue and epilogue use the
|
||
same registers to access A and C, since doing otherwise will confuse
|
||
the aliasing code. */
|
||
|
||
/* Set up ACCESSOR for accessing region B above. If the frame pointer
|
||
isn't used, the same method will serve for C. */
|
||
accessor.op = FRV_STORE;
|
||
if (frame_pointer_needed && info->total_size > 2048)
|
||
{
|
||
rtx insn;
|
||
|
||
accessor.base = gen_rtx_REG (Pmode, OLD_SP_REGNO);
|
||
accessor.base_offset = info->total_size;
|
||
insn = emit_insn (gen_movsi (accessor.base, sp));
|
||
}
|
||
else
|
||
{
|
||
accessor.base = stack_pointer_rtx;
|
||
accessor.base_offset = 0;
|
||
}
|
||
|
||
/* Allocate the stack space. */
|
||
{
|
||
rtx asm_offset = frv_frame_offset_rtx (-info->total_size);
|
||
rtx dwarf_offset = GEN_INT (-info->total_size);
|
||
|
||
frv_frame_insn (gen_stack_adjust (sp, sp, asm_offset),
|
||
gen_rtx_SET (Pmode,
|
||
sp,
|
||
gen_rtx_PLUS (Pmode, sp, dwarf_offset)));
|
||
}
|
||
|
||
/* If the frame pointer is needed, store the old one at (sp + FP_OFFSET)
|
||
and point the new one to that location. */
|
||
if (frame_pointer_needed)
|
||
{
|
||
int fp_offset = info->reg_offset[FRAME_POINTER_REGNUM];
|
||
|
||
/* ASM_SRC and DWARF_SRC both point to the frame header. ASM_SRC is
|
||
based on ACCESSOR.BASE but DWARF_SRC is always based on the stack
|
||
pointer. */
|
||
rtx asm_src = plus_constant (accessor.base,
|
||
fp_offset - accessor.base_offset);
|
||
rtx dwarf_src = plus_constant (sp, fp_offset);
|
||
|
||
/* Store the old frame pointer at (sp + FP_OFFSET). */
|
||
frv_frame_access (&accessor, fp, fp_offset);
|
||
|
||
/* Set up the new frame pointer. */
|
||
frv_frame_insn (gen_rtx_SET (VOIDmode, fp, asm_src),
|
||
gen_rtx_SET (VOIDmode, fp, dwarf_src));
|
||
|
||
/* Access region C from the frame pointer. */
|
||
accessor.base = fp;
|
||
accessor.base_offset = fp_offset;
|
||
}
|
||
|
||
/* Set up region C. */
|
||
frv_frame_access_multi (&accessor, info, STACK_REGS_STRUCT);
|
||
frv_frame_access_multi (&accessor, info, STACK_REGS_LR);
|
||
frv_frame_access_multi (&accessor, info, STACK_REGS_STDARG);
|
||
|
||
/* Set up region A. */
|
||
frv_frame_access_standard_regs (FRV_STORE, info);
|
||
|
||
/* If this is a varargs/stdarg function, issue a blockage to prevent the
|
||
scheduler from moving loads before the stores saving the registers. */
|
||
if (info->stdarg_size > 0)
|
||
emit_insn (gen_blockage ());
|
||
|
||
/* Set up pic register/small data register for this function. */
|
||
if (flag_pic && cfun->uses_pic_offset_table)
|
||
emit_insn (gen_pic_prologue (gen_rtx_REG (Pmode, PIC_REGNO),
|
||
gen_rtx_REG (Pmode, LR_REGNO),
|
||
gen_rtx_REG (SImode, OFFSET_REGNO)));
|
||
}
|
||
|
||
|
||
/* Under frv, all of the work is done via frv_expand_epilogue, but
|
||
this function provides a convient place to do cleanup. */
|
||
|
||
static void
|
||
frv_function_epilogue (file, size)
|
||
FILE *file ATTRIBUTE_UNUSED;
|
||
HOST_WIDE_INT size ATTRIBUTE_UNUSED;
|
||
{
|
||
frv_stack_cache = (frv_stack_t *)0;
|
||
|
||
/* zap last used registers for conditional execution. */
|
||
memset ((PTR) &frv_ifcvt.tmp_reg, 0, sizeof (frv_ifcvt.tmp_reg));
|
||
|
||
/* release the bitmap of created insns. */
|
||
BITMAP_XFREE (frv_ifcvt.scratch_insns_bitmap);
|
||
}
|
||
|
||
|
||
/* Called after register allocation to add any instructions needed for the
|
||
epilogue. Using an epilogue insn is favored compared to putting all of the
|
||
instructions in the FUNCTION_PROLOGUE macro, since it allows the scheduler
|
||
to intermix instructions with the saves of the caller saved registers. In
|
||
some cases, it might be necessary to emit a barrier instruction as the last
|
||
insn to prevent such scheduling.
|
||
|
||
If SIBCALL_P is true, the final branch back to the calling function is
|
||
omitted, and is used for sibling call (aka tail call) sites. For sibcalls,
|
||
we must not clobber any arguments used for parameter passing or any stack
|
||
slots for arguments passed to the current function. */
|
||
|
||
void
|
||
frv_expand_epilogue (sibcall_p)
|
||
int sibcall_p;
|
||
{
|
||
frv_stack_t *info = frv_stack_info ();
|
||
rtx fp = frame_pointer_rtx;
|
||
rtx sp = stack_pointer_rtx;
|
||
rtx return_addr;
|
||
int fp_offset;
|
||
|
||
fp_offset = info->reg_offset[FRAME_POINTER_REGNUM];
|
||
|
||
/* Restore the stack pointer to its original value if alloca or the like
|
||
is used. */
|
||
if (! current_function_sp_is_unchanging)
|
||
emit_insn (gen_addsi3 (sp, fp, frv_frame_offset_rtx (-fp_offset)));
|
||
|
||
/* Restore the callee-saved registers that were used in this function. */
|
||
frv_frame_access_standard_regs (FRV_LOAD, info);
|
||
|
||
/* Set RETURN_ADDR to the address we should return to. Set it to NULL if
|
||
no return instruction should be emitted. */
|
||
if (sibcall_p)
|
||
return_addr = 0;
|
||
else if (info->save_p[LR_REGNO])
|
||
{
|
||
int lr_offset;
|
||
rtx mem;
|
||
|
||
/* Use the same method to access the link register's slot as we did in
|
||
the prologue. In other words, use the frame pointer if available,
|
||
otherwise use the stack pointer.
|
||
|
||
LR_OFFSET is the offset of the link register's slot from the start
|
||
of the frame and MEM is a memory rtx for it. */
|
||
lr_offset = info->reg_offset[LR_REGNO];
|
||
if (frame_pointer_needed)
|
||
mem = frv_frame_mem (Pmode, fp, lr_offset - fp_offset);
|
||
else
|
||
mem = frv_frame_mem (Pmode, sp, lr_offset);
|
||
|
||
/* Load the old link register into a GPR. */
|
||
return_addr = gen_rtx_REG (Pmode, TEMP_REGNO);
|
||
emit_insn (gen_rtx_SET (VOIDmode, return_addr, mem));
|
||
}
|
||
else
|
||
return_addr = gen_rtx_REG (Pmode, LR_REGNO);
|
||
|
||
/* Restore the old frame pointer. Emit a USE afterwards to make sure
|
||
the load is preserved. */
|
||
if (frame_pointer_needed)
|
||
{
|
||
emit_insn (gen_rtx_SET (VOIDmode, fp, gen_rtx_MEM (Pmode, fp)));
|
||
emit_insn (gen_rtx_USE (VOIDmode, fp));
|
||
}
|
||
|
||
/* Deallocate the stack frame. */
|
||
if (info->total_size != 0)
|
||
{
|
||
rtx offset = frv_frame_offset_rtx (info->total_size);
|
||
emit_insn (gen_stack_adjust (sp, sp, offset));
|
||
}
|
||
|
||
/* If this function uses eh_return, add the final stack adjustment now. */
|
||
if (current_function_calls_eh_return)
|
||
emit_insn (gen_stack_adjust (sp, sp, EH_RETURN_STACKADJ_RTX));
|
||
|
||
if (return_addr)
|
||
emit_jump_insn (gen_epilogue_return (return_addr));
|
||
}
|
||
|
||
|
||
/* A C compound statement that outputs the assembler code for a thunk function,
|
||
used to implement C++ virtual function calls with multiple inheritance. The
|
||
thunk acts as a wrapper around a virtual function, adjusting the implicit
|
||
object parameter before handing control off to the real function.
|
||
|
||
First, emit code to add the integer DELTA to the location that contains the
|
||
incoming first argument. Assume that this argument contains a pointer, and
|
||
is the one used to pass the `this' pointer in C++. This is the incoming
|
||
argument *before* the function prologue, e.g. `%o0' on a sparc. The
|
||
addition must preserve the values of all other incoming arguments.
|
||
|
||
After the addition, emit code to jump to FUNCTION, which is a
|
||
`FUNCTION_DECL'. This is a direct pure jump, not a call, and does not touch
|
||
the return address. Hence returning from FUNCTION will return to whoever
|
||
called the current `thunk'.
|
||
|
||
The effect must be as if FUNCTION had been called directly with the adjusted
|
||
first argument. This macro is responsible for emitting all of the code for
|
||
a thunk function; `FUNCTION_PROLOGUE' and `FUNCTION_EPILOGUE' are not
|
||
invoked.
|
||
|
||
The THUNK_FNDECL is redundant. (DELTA and FUNCTION have already been
|
||
extracted from it.) It might possibly be useful on some targets, but
|
||
probably not.
|
||
|
||
If you do not define this macro, the target-independent code in the C++
|
||
frontend will generate a less efficient heavyweight thunk that calls
|
||
FUNCTION instead of jumping to it. The generic approach does not support
|
||
varargs. */
|
||
|
||
static void
|
||
frv_asm_output_mi_thunk (file, thunk_fndecl, delta, vcall_offset, function)
|
||
FILE *file;
|
||
tree thunk_fndecl ATTRIBUTE_UNUSED;
|
||
HOST_WIDE_INT delta;
|
||
HOST_WIDE_INT vcall_offset ATTRIBUTE_UNUSED;
|
||
tree function;
|
||
{
|
||
const char *name_func = XSTR (XEXP (DECL_RTL (function), 0), 0);
|
||
const char *name_arg0 = reg_names[FIRST_ARG_REGNUM];
|
||
const char *name_jmp = reg_names[JUMP_REGNO];
|
||
const char *parallel = ((PACKING_FLAG_USED_P ()) ? ".p" : "");
|
||
|
||
/* Do the add using an addi if possible */
|
||
if (IN_RANGE_P (delta, -2048, 2047))
|
||
fprintf (file, "\taddi %s,#%d,%s\n", name_arg0, (int) delta, name_arg0);
|
||
else
|
||
{
|
||
const char *name_add = reg_names[TEMP_REGNO];
|
||
fprintf (file, "\tsethi%s #hi(", parallel);
|
||
fprintf (file, HOST_WIDE_INT_PRINT_DEC, delta);
|
||
fprintf (file, "),%s\n", name_add);
|
||
fprintf (file, "\tsetlo #lo(");
|
||
fprintf (file, HOST_WIDE_INT_PRINT_DEC, delta);
|
||
fprintf (file, "),%s\n", name_add);
|
||
fprintf (file, "\tadd %s,%s,%s\n", name_add, name_arg0, name_arg0);
|
||
}
|
||
|
||
if (!flag_pic)
|
||
{
|
||
fprintf (file, "\tsethi%s #hi(", parallel);
|
||
assemble_name (file, name_func);
|
||
fprintf (file, "),%s\n", name_jmp);
|
||
|
||
fprintf (file, "\tsetlo #lo(");
|
||
assemble_name (file, name_func);
|
||
fprintf (file, "),%s\n", name_jmp);
|
||
}
|
||
else
|
||
{
|
||
/* Use JUMP_REGNO as a temporary PIC register. */
|
||
const char *name_lr = reg_names[LR_REGNO];
|
||
const char *name_gppic = name_jmp;
|
||
const char *name_tmp = reg_names[TEMP_REGNO];
|
||
|
||
fprintf (file, "\tmovsg %s,%s\n", name_lr, name_tmp);
|
||
fprintf (file, "\tcall 1f\n");
|
||
fprintf (file, "1:\tmovsg %s,%s\n", name_lr, name_gppic);
|
||
fprintf (file, "\tmovgs %s,%s\n", name_tmp, name_lr);
|
||
fprintf (file, "\tsethi%s #gprelhi(1b),%s\n", parallel, name_tmp);
|
||
fprintf (file, "\tsetlo #gprello(1b),%s\n", name_tmp);
|
||
fprintf (file, "\tsub %s,%s,%s\n", name_gppic, name_tmp, name_gppic);
|
||
|
||
fprintf (file, "\tsethi%s #gprelhi(", parallel);
|
||
assemble_name (file, name_func);
|
||
fprintf (file, "),%s\n", name_tmp);
|
||
|
||
fprintf (file, "\tsetlo #gprello(");
|
||
assemble_name (file, name_func);
|
||
fprintf (file, "),%s\n", name_tmp);
|
||
|
||
fprintf (file, "\tadd %s,%s,%s\n", name_gppic, name_tmp, name_jmp);
|
||
}
|
||
|
||
/* Jump to the function address */
|
||
fprintf (file, "\tjmpl @(%s,%s)\n", name_jmp, reg_names[GPR_FIRST+0]);
|
||
}
|
||
|
||
|
||
/* A C expression which is nonzero if a function must have and use a frame
|
||
pointer. This expression is evaluated in the reload pass. If its value is
|
||
nonzero the function will have a frame pointer.
|
||
|
||
The expression can in principle examine the current function and decide
|
||
according to the facts, but on most machines the constant 0 or the constant
|
||
1 suffices. Use 0 when the machine allows code to be generated with no
|
||
frame pointer, and doing so saves some time or space. Use 1 when there is
|
||
no possible advantage to avoiding a frame pointer.
|
||
|
||
In certain cases, the compiler does not know how to produce valid code
|
||
without a frame pointer. The compiler recognizes those cases and
|
||
automatically gives the function a frame pointer regardless of what
|
||
`FRAME_POINTER_REQUIRED' says. You don't need to worry about them.
|
||
|
||
In a function that does not require a frame pointer, the frame pointer
|
||
register can be allocated for ordinary usage, unless you mark it as a fixed
|
||
register. See `FIXED_REGISTERS' for more information. */
|
||
|
||
/* On frv, create a frame whenever we need to create stack */
|
||
|
||
int
|
||
frv_frame_pointer_required ()
|
||
{
|
||
if (! current_function_is_leaf)
|
||
return TRUE;
|
||
|
||
if (get_frame_size () != 0)
|
||
return TRUE;
|
||
|
||
if (cfun->stdarg)
|
||
return TRUE;
|
||
|
||
if (!current_function_sp_is_unchanging)
|
||
return TRUE;
|
||
|
||
if (flag_pic && cfun->uses_pic_offset_table)
|
||
return TRUE;
|
||
|
||
if (profile_flag)
|
||
return TRUE;
|
||
|
||
if (cfun->machine->frame_needed)
|
||
return TRUE;
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
/* This macro is similar to `INITIAL_FRAME_POINTER_OFFSET'. It specifies the
|
||
initial difference between the specified pair of registers. This macro must
|
||
be defined if `ELIMINABLE_REGS' is defined. */
|
||
|
||
/* See frv_stack_info for more details on the frv stack frame. */
|
||
|
||
int
|
||
frv_initial_elimination_offset (from, to)
|
||
int from;
|
||
int to;
|
||
{
|
||
frv_stack_t *info = frv_stack_info ();
|
||
int ret = 0;
|
||
|
||
if (to == STACK_POINTER_REGNUM && from == ARG_POINTER_REGNUM)
|
||
ret = info->total_size - info->pretend_size;
|
||
|
||
else if (to == STACK_POINTER_REGNUM && from == FRAME_POINTER_REGNUM)
|
||
ret = - info->reg_offset[FRAME_POINTER_REGNUM];
|
||
|
||
else if (to == FRAME_POINTER_REGNUM && from == ARG_POINTER_REGNUM)
|
||
ret = (info->total_size
|
||
- info->reg_offset[FRAME_POINTER_REGNUM]
|
||
- info->pretend_size);
|
||
|
||
else
|
||
abort ();
|
||
|
||
if (TARGET_DEBUG_STACK)
|
||
fprintf (stderr, "Eliminate %s to %s by adding %d\n",
|
||
reg_names [from], reg_names[to], ret);
|
||
|
||
return ret;
|
||
}
|
||
|
||
|
||
/* This macro offers an alternative to using `__builtin_saveregs' and defining
|
||
the macro `EXPAND_BUILTIN_SAVEREGS'. Use it to store the anonymous register
|
||
arguments into the stack so that all the arguments appear to have been
|
||
passed consecutively on the stack. Once this is done, you can use the
|
||
standard implementation of varargs that works for machines that pass all
|
||
their arguments on the stack.
|
||
|
||
The argument ARGS_SO_FAR is the `CUMULATIVE_ARGS' data structure, containing
|
||
the values that obtain after processing of the named arguments. The
|
||
arguments MODE and TYPE describe the last named argument--its machine mode
|
||
and its data type as a tree node.
|
||
|
||
The macro implementation should do two things: first, push onto the stack
|
||
all the argument registers *not* used for the named arguments, and second,
|
||
store the size of the data thus pushed into the `int'-valued variable whose
|
||
name is supplied as the argument PRETEND_ARGS_SIZE. The value that you
|
||
store here will serve as additional offset for setting up the stack frame.
|
||
|
||
Because you must generate code to push the anonymous arguments at compile
|
||
time without knowing their data types, `SETUP_INCOMING_VARARGS' is only
|
||
useful on machines that have just a single category of argument register and
|
||
use it uniformly for all data types.
|
||
|
||
If the argument SECOND_TIME is nonzero, it means that the arguments of the
|
||
function are being analyzed for the second time. This happens for an inline
|
||
function, which is not actually compiled until the end of the source file.
|
||
The macro `SETUP_INCOMING_VARARGS' should not generate any instructions in
|
||
this case. */
|
||
|
||
void
|
||
frv_setup_incoming_varargs (cum, mode, type, pretend_size, second_time)
|
||
CUMULATIVE_ARGS *cum;
|
||
enum machine_mode mode;
|
||
tree type ATTRIBUTE_UNUSED;
|
||
int *pretend_size;
|
||
int second_time;
|
||
{
|
||
if (TARGET_DEBUG_ARG)
|
||
fprintf (stderr,
|
||
"setup_vararg: words = %2d, mode = %4s, pretend_size = %d, second_time = %d\n",
|
||
*cum, GET_MODE_NAME (mode), *pretend_size, second_time);
|
||
}
|
||
|
||
|
||
/* If defined, is a C expression that produces the machine-specific code for a
|
||
call to `__builtin_saveregs'. This code will be moved to the very beginning
|
||
of the function, before any parameter access are made. The return value of
|
||
this function should be an RTX that contains the value to use as the return
|
||
of `__builtin_saveregs'.
|
||
|
||
If this macro is not defined, the compiler will output an ordinary call to
|
||
the library function `__builtin_saveregs'. */
|
||
|
||
rtx
|
||
frv_expand_builtin_saveregs ()
|
||
{
|
||
int offset = UNITS_PER_WORD * FRV_NUM_ARG_REGS;
|
||
|
||
if (TARGET_DEBUG_ARG)
|
||
fprintf (stderr, "expand_builtin_saveregs: offset from ap = %d\n",
|
||
offset);
|
||
|
||
return gen_rtx (PLUS, Pmode, virtual_incoming_args_rtx, GEN_INT (- offset));
|
||
}
|
||
|
||
|
||
/* Expand __builtin_va_start to do the va_start macro. */
|
||
|
||
void
|
||
frv_expand_builtin_va_start (valist, nextarg)
|
||
tree valist;
|
||
rtx nextarg;
|
||
{
|
||
tree t;
|
||
int num = cfun->args_info - FIRST_ARG_REGNUM - FRV_NUM_ARG_REGS;
|
||
|
||
nextarg = gen_rtx_PLUS (Pmode, virtual_incoming_args_rtx,
|
||
GEN_INT (UNITS_PER_WORD * num));
|
||
|
||
if (TARGET_DEBUG_ARG)
|
||
{
|
||
fprintf (stderr, "va_start: args_info = %d, num = %d\n",
|
||
cfun->args_info, num);
|
||
|
||
debug_rtx (nextarg);
|
||
}
|
||
|
||
t = build (MODIFY_EXPR, TREE_TYPE (valist), valist,
|
||
make_tree (ptr_type_node, nextarg));
|
||
TREE_SIDE_EFFECTS (t) = 1;
|
||
|
||
expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);
|
||
}
|
||
|
||
|
||
/* Expand __builtin_va_arg to do the va_arg macro. */
|
||
|
||
rtx
|
||
frv_expand_builtin_va_arg(valist, type)
|
||
tree valist;
|
||
tree type;
|
||
{
|
||
rtx addr;
|
||
rtx mem;
|
||
rtx reg;
|
||
|
||
if (TARGET_DEBUG_ARG)
|
||
{
|
||
fprintf (stderr, "va_arg:\n");
|
||
debug_tree (type);
|
||
}
|
||
|
||
if (! AGGREGATE_TYPE_P (type))
|
||
return std_expand_builtin_va_arg (valist, type);
|
||
|
||
addr = std_expand_builtin_va_arg (valist, ptr_type_node);
|
||
mem = gen_rtx_MEM (Pmode, addr);
|
||
reg = gen_reg_rtx (Pmode);
|
||
|
||
set_mem_alias_set (mem, get_varargs_alias_set ());
|
||
emit_move_insn (reg, mem);
|
||
|
||
return reg;
|
||
}
|
||
|
||
|
||
/* Expand a block move operation, and return 1 if successful. Return 0
|
||
if we should let the compiler generate normal code.
|
||
|
||
operands[0] is the destination
|
||
operands[1] is the source
|
||
operands[2] is the length
|
||
operands[3] is the alignment */
|
||
|
||
/* Maximum number of loads to do before doing the stores */
|
||
#ifndef MAX_MOVE_REG
|
||
#define MAX_MOVE_REG 4
|
||
#endif
|
||
|
||
/* Maximum number of total loads to do. */
|
||
#ifndef TOTAL_MOVE_REG
|
||
#define TOTAL_MOVE_REG 8
|
||
#endif
|
||
|
||
int
|
||
frv_expand_block_move (operands)
|
||
rtx operands[];
|
||
{
|
||
rtx orig_dest = operands[0];
|
||
rtx orig_src = operands[1];
|
||
rtx bytes_rtx = operands[2];
|
||
rtx align_rtx = operands[3];
|
||
int constp = (GET_CODE (bytes_rtx) == CONST_INT);
|
||
int align;
|
||
int bytes;
|
||
int offset;
|
||
int num_reg;
|
||
int i;
|
||
rtx src_reg;
|
||
rtx dest_reg;
|
||
rtx src_addr;
|
||
rtx dest_addr;
|
||
rtx src_mem;
|
||
rtx dest_mem;
|
||
rtx tmp_reg;
|
||
rtx stores[MAX_MOVE_REG];
|
||
int move_bytes;
|
||
enum machine_mode mode;
|
||
|
||
/* If this is not a fixed size move, just call memcpy */
|
||
if (! constp)
|
||
return FALSE;
|
||
|
||
/* If this is not a fixed size alignment, abort */
|
||
if (GET_CODE (align_rtx) != CONST_INT)
|
||
abort ();
|
||
|
||
align = INTVAL (align_rtx);
|
||
|
||
/* Anything to move? */
|
||
bytes = INTVAL (bytes_rtx);
|
||
if (bytes <= 0)
|
||
return TRUE;
|
||
|
||
/* Don't support real large moves. */
|
||
if (bytes > TOTAL_MOVE_REG*align)
|
||
return FALSE;
|
||
|
||
/* Move the address into scratch registers. */
|
||
dest_reg = copy_addr_to_reg (XEXP (orig_dest, 0));
|
||
src_reg = copy_addr_to_reg (XEXP (orig_src, 0));
|
||
|
||
num_reg = offset = 0;
|
||
for ( ; bytes > 0; (bytes -= move_bytes), (offset += move_bytes))
|
||
{
|
||
/* Calculate the correct offset for src/dest */
|
||
if (offset == 0)
|
||
{
|
||
src_addr = src_reg;
|
||
dest_addr = dest_reg;
|
||
}
|
||
else
|
||
{
|
||
src_addr = plus_constant (src_reg, offset);
|
||
dest_addr = plus_constant (dest_reg, offset);
|
||
}
|
||
|
||
/* Generate the appropriate load and store, saving the stores
|
||
for later. */
|
||
if (bytes >= 4 && align >= 4)
|
||
mode = SImode;
|
||
else if (bytes >= 2 && align >= 2)
|
||
mode = HImode;
|
||
else
|
||
mode = QImode;
|
||
|
||
move_bytes = GET_MODE_SIZE (mode);
|
||
tmp_reg = gen_reg_rtx (mode);
|
||
src_mem = change_address (orig_src, mode, src_addr);
|
||
dest_mem = change_address (orig_dest, mode, dest_addr);
|
||
emit_insn (gen_rtx_SET (VOIDmode, tmp_reg, src_mem));
|
||
stores[num_reg++] = gen_rtx_SET (VOIDmode, dest_mem, tmp_reg);
|
||
|
||
if (num_reg >= MAX_MOVE_REG)
|
||
{
|
||
for (i = 0; i < num_reg; i++)
|
||
emit_insn (stores[i]);
|
||
num_reg = 0;
|
||
}
|
||
}
|
||
|
||
for (i = 0; i < num_reg; i++)
|
||
emit_insn (stores[i]);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
/* Expand a block clear operation, and return 1 if successful. Return 0
|
||
if we should let the compiler generate normal code.
|
||
|
||
operands[0] is the destination
|
||
operands[1] is the length
|
||
operands[2] is the alignment */
|
||
|
||
int
|
||
frv_expand_block_clear (operands)
|
||
rtx operands[];
|
||
{
|
||
rtx orig_dest = operands[0];
|
||
rtx bytes_rtx = operands[1];
|
||
rtx align_rtx = operands[2];
|
||
int constp = (GET_CODE (bytes_rtx) == CONST_INT);
|
||
int align;
|
||
int bytes;
|
||
int offset;
|
||
int num_reg;
|
||
rtx dest_reg;
|
||
rtx dest_addr;
|
||
rtx dest_mem;
|
||
int clear_bytes;
|
||
enum machine_mode mode;
|
||
|
||
/* If this is not a fixed size move, just call memcpy */
|
||
if (! constp)
|
||
return FALSE;
|
||
|
||
/* If this is not a fixed size alignment, abort */
|
||
if (GET_CODE (align_rtx) != CONST_INT)
|
||
abort ();
|
||
|
||
align = INTVAL (align_rtx);
|
||
|
||
/* Anything to move? */
|
||
bytes = INTVAL (bytes_rtx);
|
||
if (bytes <= 0)
|
||
return TRUE;
|
||
|
||
/* Don't support real large clears. */
|
||
if (bytes > TOTAL_MOVE_REG*align)
|
||
return FALSE;
|
||
|
||
/* Move the address into a scratch register. */
|
||
dest_reg = copy_addr_to_reg (XEXP (orig_dest, 0));
|
||
|
||
num_reg = offset = 0;
|
||
for ( ; bytes > 0; (bytes -= clear_bytes), (offset += clear_bytes))
|
||
{
|
||
/* Calculate the correct offset for src/dest */
|
||
dest_addr = ((offset == 0)
|
||
? dest_reg
|
||
: plus_constant (dest_reg, offset));
|
||
|
||
/* Generate the appropriate store of gr0 */
|
||
if (bytes >= 4 && align >= 4)
|
||
mode = SImode;
|
||
else if (bytes >= 2 && align >= 2)
|
||
mode = HImode;
|
||
else
|
||
mode = QImode;
|
||
|
||
clear_bytes = GET_MODE_SIZE (mode);
|
||
dest_mem = change_address (orig_dest, mode, dest_addr);
|
||
emit_insn (gen_rtx_SET (VOIDmode, dest_mem, const0_rtx));
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
/* The following variable is used to output modifiers of assembler
|
||
code of the current output insn.. */
|
||
|
||
static rtx *frv_insn_operands;
|
||
|
||
/* The following function is used to add assembler insn code suffix .p
|
||
if it is necessary. */
|
||
|
||
const char *
|
||
frv_asm_output_opcode (f, ptr)
|
||
FILE *f;
|
||
const char *ptr;
|
||
{
|
||
int c;
|
||
|
||
if (! PACKING_FLAG_USED_P())
|
||
return ptr;
|
||
|
||
for (; *ptr && *ptr != ' ' && *ptr != '\t';)
|
||
{
|
||
c = *ptr++;
|
||
if (c == '%' && ((*ptr >= 'a' && *ptr <= 'z')
|
||
|| (*ptr >= 'A' && *ptr <= 'Z')))
|
||
{
|
||
int letter = *ptr++;
|
||
|
||
c = atoi (ptr);
|
||
frv_print_operand (f, frv_insn_operands [c], letter);
|
||
while ((c = *ptr) >= '0' && c <= '9')
|
||
ptr++;
|
||
}
|
||
else
|
||
fputc (c, f);
|
||
}
|
||
|
||
if (!frv_insn_packing_flag)
|
||
fprintf (f, ".p");
|
||
|
||
return ptr;
|
||
}
|
||
|
||
/* The following function sets up the packing bit for the current
|
||
output insn. Remember that the function is not called for asm
|
||
insns. */
|
||
|
||
void
|
||
frv_final_prescan_insn (insn, opvec, noperands)
|
||
rtx insn;
|
||
rtx *opvec;
|
||
int noperands ATTRIBUTE_UNUSED;
|
||
{
|
||
if (! PACKING_FLAG_USED_P())
|
||
return;
|
||
|
||
if (GET_RTX_CLASS (GET_CODE (insn)) != 'i')
|
||
return;
|
||
|
||
frv_insn_operands = opvec;
|
||
|
||
/* Look for the next printable instruction. frv_pack_insns () has set
|
||
things up so that any printable instruction will have TImode if it
|
||
starts a new packet and VOIDmode if it should be packed with the
|
||
previous instruction.
|
||
|
||
Printable instructions will be asm_operands or match one of the .md
|
||
patterns. Since asm instructions cannot be packed -- and will
|
||
therefore have TImode -- this loop terminates on any recognisable
|
||
instruction, and on any unrecognisable instruction with TImode. */
|
||
for (insn = NEXT_INSN (insn); insn; insn = NEXT_INSN (insn))
|
||
{
|
||
if (NOTE_P (insn))
|
||
continue;
|
||
else if (!INSN_P (insn))
|
||
break;
|
||
else if (GET_MODE (insn) == TImode || INSN_CODE (insn) != -1)
|
||
break;
|
||
}
|
||
|
||
/* Set frv_insn_packing_flag to FALSE if the next instruction should
|
||
be packed with this one. Set it to TRUE otherwise. If the next
|
||
instruction is an asm insntruction, this statement will set the
|
||
flag to TRUE, and that value will still hold when the asm operands
|
||
themselves are printed. */
|
||
frv_insn_packing_flag = ! (insn && INSN_P (insn)
|
||
&& GET_MODE (insn) != TImode);
|
||
}
|
||
|
||
|
||
|
||
/* A C expression whose value is RTL representing the address in a stack frame
|
||
where the pointer to the caller's frame is stored. Assume that FRAMEADDR is
|
||
an RTL expression for the address of the stack frame itself.
|
||
|
||
If you don't define this macro, the default is to return the value of
|
||
FRAMEADDR--that is, the stack frame address is also the address of the stack
|
||
word that points to the previous frame. */
|
||
|
||
/* The default is correct, but we need to make sure the frame gets created. */
|
||
rtx
|
||
frv_dynamic_chain_address (frame)
|
||
rtx frame;
|
||
{
|
||
cfun->machine->frame_needed = 1;
|
||
return frame;
|
||
}
|
||
|
||
|
||
/* A C expression whose value is RTL representing the value of the return
|
||
address for the frame COUNT steps up from the current frame, after the
|
||
prologue. FRAMEADDR is the frame pointer of the COUNT frame, or the frame
|
||
pointer of the COUNT - 1 frame if `RETURN_ADDR_IN_PREVIOUS_FRAME' is
|
||
defined.
|
||
|
||
The value of the expression must always be the correct address when COUNT is
|
||
zero, but may be `NULL_RTX' if there is not way to determine the return
|
||
address of other frames. */
|
||
|
||
rtx
|
||
frv_return_addr_rtx (count, frame)
|
||
int count ATTRIBUTE_UNUSED;
|
||
rtx frame;
|
||
{
|
||
cfun->machine->frame_needed = 1;
|
||
return gen_rtx_MEM (Pmode, plus_constant (frame, 8));
|
||
}
|
||
|
||
/* Given a memory reference MEMREF, interpret the referenced memory as
|
||
an array of MODE values, and return a reference to the element
|
||
specified by INDEX. Assume that any pre-modification implicit in
|
||
MEMREF has already happened.
|
||
|
||
MEMREF must be a legitimate operand for modes larger than SImode.
|
||
GO_IF_LEGITIMATE_ADDRESS forbids register+register addresses, which
|
||
this function cannot handle. */
|
||
rtx
|
||
frv_index_memory (memref, mode, index)
|
||
rtx memref;
|
||
enum machine_mode mode;
|
||
int index;
|
||
{
|
||
rtx base = XEXP (memref, 0);
|
||
if (GET_CODE (base) == PRE_MODIFY)
|
||
base = XEXP (base, 0);
|
||
return change_address (memref, mode,
|
||
plus_constant (base, index * GET_MODE_SIZE (mode)));
|
||
}
|
||
|
||
|
||
/* Print a memory address as an operand to reference that memory location. */
|
||
void
|
||
frv_print_operand_address (stream, x)
|
||
FILE * stream;
|
||
rtx x;
|
||
{
|
||
if (GET_CODE (x) == MEM)
|
||
x = XEXP (x, 0);
|
||
|
||
switch (GET_CODE (x))
|
||
{
|
||
case REG:
|
||
fputs (reg_names [ REGNO (x)], stream);
|
||
return;
|
||
|
||
case CONST_INT:
|
||
fprintf (stream, "%ld", (long) INTVAL (x));
|
||
return;
|
||
|
||
case SYMBOL_REF:
|
||
assemble_name (stream, XSTR (x, 0));
|
||
return;
|
||
|
||
case LABEL_REF:
|
||
case CONST:
|
||
output_addr_const (stream, x);
|
||
return;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
|
||
fatal_insn ("Bad insn to frv_print_operand_address:", x);
|
||
}
|
||
|
||
|
||
static void
|
||
frv_print_operand_memory_reference_reg (stream, x)
|
||
FILE *stream;
|
||
rtx x;
|
||
{
|
||
int regno = true_regnum (x);
|
||
if (GPR_P (regno))
|
||
fputs (reg_names[regno], stream);
|
||
else
|
||
fatal_insn ("Bad register to frv_print_operand_memory_reference_reg:", x);
|
||
}
|
||
|
||
/* Print a memory reference suitable for the ld/st instructions. */
|
||
|
||
static void
|
||
frv_print_operand_memory_reference (stream, x, addr_offset)
|
||
FILE *stream;
|
||
rtx x;
|
||
int addr_offset;
|
||
{
|
||
rtx x0 = NULL_RTX;
|
||
rtx x1 = NULL_RTX;
|
||
|
||
switch (GET_CODE (x))
|
||
{
|
||
case SUBREG:
|
||
case REG:
|
||
x0 = x;
|
||
break;
|
||
|
||
case PRE_MODIFY: /* (pre_modify (reg) (plus (reg) (reg))) */
|
||
x0 = XEXP (x, 0);
|
||
x1 = XEXP (XEXP (x, 1), 1);
|
||
break;
|
||
|
||
case CONST_INT:
|
||
x1 = x;
|
||
break;
|
||
|
||
case PLUS:
|
||
x0 = XEXP (x, 0);
|
||
x1 = XEXP (x, 1);
|
||
if (GET_CODE (x0) == CONST_INT)
|
||
{
|
||
x0 = XEXP (x, 1);
|
||
x1 = XEXP (x, 0);
|
||
}
|
||
break;
|
||
|
||
default:
|
||
fatal_insn ("Bad insn to frv_print_operand_memory_reference:", x);
|
||
break;
|
||
|
||
}
|
||
|
||
if (addr_offset)
|
||
{
|
||
if (!x1)
|
||
x1 = const0_rtx;
|
||
else if (GET_CODE (x1) != CONST_INT)
|
||
fatal_insn ("Bad insn to frv_print_operand_memory_reference:", x);
|
||
}
|
||
|
||
fputs ("@(", stream);
|
||
if (!x0)
|
||
fputs (reg_names[GPR_R0], stream);
|
||
else if (GET_CODE (x0) == REG || GET_CODE (x0) == SUBREG)
|
||
frv_print_operand_memory_reference_reg (stream, x0);
|
||
else
|
||
fatal_insn ("Bad insn to frv_print_operand_memory_reference:", x);
|
||
|
||
fputs (",", stream);
|
||
if (!x1)
|
||
fputs (reg_names [GPR_R0], stream);
|
||
|
||
else
|
||
{
|
||
switch (GET_CODE (x1))
|
||
{
|
||
case SUBREG:
|
||
case REG:
|
||
frv_print_operand_memory_reference_reg (stream, x1);
|
||
break;
|
||
|
||
case CONST_INT:
|
||
fprintf (stream, "%ld", (long) (INTVAL (x1) + addr_offset));
|
||
break;
|
||
|
||
case SYMBOL_REF:
|
||
if (x0 && GET_CODE (x0) == REG && REGNO (x0) == SDA_BASE_REG
|
||
&& symbol_ref_small_data_p (x1))
|
||
{
|
||
fputs ("#gprel12(", stream);
|
||
assemble_name (stream, XSTR (x1, 0));
|
||
fputs (")", stream);
|
||
}
|
||
else
|
||
fatal_insn ("Bad insn to frv_print_operand_memory_reference:", x);
|
||
break;
|
||
|
||
case CONST:
|
||
if (x0 && GET_CODE (x0) == REG && REGNO (x0) == SDA_BASE_REG
|
||
&& const_small_data_p (x1))
|
||
{
|
||
fputs ("#gprel12(", stream);
|
||
assemble_name (stream, XSTR (XEXP (XEXP (x1, 0), 0), 0));
|
||
fprintf (stream, "+%d)", INTVAL (XEXP (XEXP (x1, 0), 1)));
|
||
}
|
||
else
|
||
fatal_insn ("Bad insn to frv_print_operand_memory_reference:", x);
|
||
break;
|
||
|
||
default:
|
||
fatal_insn ("Bad insn to frv_print_operand_memory_reference:", x);
|
||
}
|
||
}
|
||
|
||
fputs (")", stream);
|
||
}
|
||
|
||
|
||
/* Return 2 for likely branches and 0 for non-likely branches */
|
||
|
||
#define FRV_JUMP_LIKELY 2
|
||
#define FRV_JUMP_NOT_LIKELY 0
|
||
|
||
static int
|
||
frv_print_operand_jump_hint (insn)
|
||
rtx insn;
|
||
{
|
||
rtx note;
|
||
rtx labelref;
|
||
int ret;
|
||
HOST_WIDE_INT prob = -1;
|
||
enum { UNKNOWN, BACKWARD, FORWARD } jump_type = UNKNOWN;
|
||
|
||
if (GET_CODE (insn) != JUMP_INSN)
|
||
abort ();
|
||
|
||
/* Assume any non-conditional jump is likely. */
|
||
if (! any_condjump_p (insn))
|
||
ret = FRV_JUMP_LIKELY;
|
||
|
||
else
|
||
{
|
||
labelref = condjump_label (insn);
|
||
if (labelref)
|
||
{
|
||
rtx label = XEXP (labelref, 0);
|
||
jump_type = (insn_current_address > INSN_ADDRESSES (INSN_UID (label))
|
||
? BACKWARD
|
||
: FORWARD);
|
||
}
|
||
|
||
note = find_reg_note (insn, REG_BR_PROB, 0);
|
||
if (!note)
|
||
ret = ((jump_type == BACKWARD) ? FRV_JUMP_LIKELY : FRV_JUMP_NOT_LIKELY);
|
||
|
||
else
|
||
{
|
||
prob = INTVAL (XEXP (note, 0));
|
||
ret = ((prob >= (REG_BR_PROB_BASE / 2))
|
||
? FRV_JUMP_LIKELY
|
||
: FRV_JUMP_NOT_LIKELY);
|
||
}
|
||
}
|
||
|
||
#if 0
|
||
if (TARGET_DEBUG)
|
||
{
|
||
char *direction;
|
||
|
||
switch (jump_type)
|
||
{
|
||
default:
|
||
case UNKNOWN: direction = "unknown jump direction"; break;
|
||
case BACKWARD: direction = "jump backward"; break;
|
||
case FORWARD: direction = "jump forward"; break;
|
||
}
|
||
|
||
fprintf (stderr,
|
||
"%s: uid %ld, %s, probability = %ld, max prob. = %ld, hint = %d\n",
|
||
IDENTIFIER_POINTER (DECL_NAME (current_function_decl)),
|
||
(long)INSN_UID (insn), direction, (long)prob,
|
||
(long)REG_BR_PROB_BASE, ret);
|
||
}
|
||
#endif
|
||
|
||
return ret;
|
||
}
|
||
|
||
|
||
/* Print an operand to an assembler instruction.
|
||
|
||
`%' followed by a letter and a digit says to output an operand in an
|
||
alternate fashion. Four letters have standard, built-in meanings described
|
||
below. The machine description macro `PRINT_OPERAND' can define additional
|
||
letters with nonstandard meanings.
|
||
|
||
`%cDIGIT' can be used to substitute an operand that is a constant value
|
||
without the syntax that normally indicates an immediate operand.
|
||
|
||
`%nDIGIT' is like `%cDIGIT' except that the value of the constant is negated
|
||
before printing.
|
||
|
||
`%aDIGIT' can be used to substitute an operand as if it were a memory
|
||
reference, with the actual operand treated as the address. This may be
|
||
useful when outputting a "load address" instruction, because often the
|
||
assembler syntax for such an instruction requires you to write the operand
|
||
as if it were a memory reference.
|
||
|
||
`%lDIGIT' is used to substitute a `label_ref' into a jump instruction.
|
||
|
||
`%=' outputs a number which is unique to each instruction in the entire
|
||
compilation. This is useful for making local labels to be referred to more
|
||
than once in a single template that generates multiple assembler
|
||
instructions.
|
||
|
||
`%' followed by a punctuation character specifies a substitution that does
|
||
not use an operand. Only one case is standard: `%%' outputs a `%' into the
|
||
assembler code. Other nonstandard cases can be defined in the
|
||
`PRINT_OPERAND' macro. You must also define which punctuation characters
|
||
are valid with the `PRINT_OPERAND_PUNCT_VALID_P' macro. */
|
||
|
||
void
|
||
frv_print_operand (file, x, code)
|
||
FILE * file;
|
||
rtx x;
|
||
int code;
|
||
{
|
||
HOST_WIDE_INT value;
|
||
int offset;
|
||
|
||
if (code != 0 && !isalpha (code))
|
||
value = 0;
|
||
|
||
else if (GET_CODE (x) == CONST_INT)
|
||
value = INTVAL (x);
|
||
|
||
else if (GET_CODE (x) == CONST_DOUBLE)
|
||
{
|
||
if (GET_MODE (x) == SFmode)
|
||
{
|
||
REAL_VALUE_TYPE rv;
|
||
long l;
|
||
|
||
REAL_VALUE_FROM_CONST_DOUBLE (rv, x);
|
||
REAL_VALUE_TO_TARGET_SINGLE (rv, l);
|
||
value = l;
|
||
}
|
||
|
||
else if (GET_MODE (x) == VOIDmode)
|
||
value = CONST_DOUBLE_LOW (x);
|
||
|
||
else
|
||
fatal_insn ("Bad insn in frv_print_operand, bad const_double", x);
|
||
}
|
||
|
||
else
|
||
value = 0;
|
||
|
||
switch (code)
|
||
{
|
||
|
||
case '.':
|
||
/* Output r0 */
|
||
fputs (reg_names[GPR_R0], file);
|
||
break;
|
||
|
||
case '#':
|
||
fprintf (file, "%d", frv_print_operand_jump_hint (current_output_insn));
|
||
break;
|
||
|
||
case SDATA_FLAG_CHAR:
|
||
/* Output small data area base register (gr16). */
|
||
fputs (reg_names[SDA_BASE_REG], file);
|
||
break;
|
||
|
||
case '~':
|
||
/* Output pic register (gr17). */
|
||
fputs (reg_names[PIC_REGNO], file);
|
||
break;
|
||
|
||
case '*':
|
||
/* Output the temporary integer CCR register */
|
||
fputs (reg_names[ICR_TEMP], file);
|
||
break;
|
||
|
||
case '&':
|
||
/* Output the temporary integer CC register */
|
||
fputs (reg_names[ICC_TEMP], file);
|
||
break;
|
||
|
||
/* case 'a': print an address */
|
||
|
||
case 'C':
|
||
/* Print appropriate test for integer branch false operation */
|
||
switch (GET_CODE (x))
|
||
{
|
||
default:
|
||
fatal_insn ("Bad insn to frv_print_operand, 'C' modifier:", x);
|
||
|
||
case EQ: fputs ("ne", file); break;
|
||
case NE: fputs ("eq", file); break;
|
||
case LT: fputs ("ge", file); break;
|
||
case LE: fputs ("gt", file); break;
|
||
case GT: fputs ("le", file); break;
|
||
case GE: fputs ("lt", file); break;
|
||
case LTU: fputs ("nc", file); break;
|
||
case LEU: fputs ("hi", file); break;
|
||
case GTU: fputs ("ls", file); break;
|
||
case GEU: fputs ("c", file); break;
|
||
}
|
||
break;
|
||
|
||
/* case 'c': print a constant without the constant prefix. If
|
||
CONSTANT_ADDRESS_P(x) is not true, PRINT_OPERAND is called. */
|
||
|
||
case 'c':
|
||
/* Print appropriate test for integer branch true operation */
|
||
switch (GET_CODE (x))
|
||
{
|
||
default:
|
||
fatal_insn ("Bad insn to frv_print_operand, 'c' modifier:", x);
|
||
|
||
case EQ: fputs ("eq", file); break;
|
||
case NE: fputs ("ne", file); break;
|
||
case LT: fputs ("lt", file); break;
|
||
case LE: fputs ("le", file); break;
|
||
case GT: fputs ("gt", file); break;
|
||
case GE: fputs ("ge", file); break;
|
||
case LTU: fputs ("c", file); break;
|
||
case LEU: fputs ("ls", file); break;
|
||
case GTU: fputs ("hi", file); break;
|
||
case GEU: fputs ("nc", file); break;
|
||
}
|
||
break;
|
||
|
||
case 'e':
|
||
/* Print 1 for a NE and 0 for an EQ to give the final argument
|
||
for a conditional instruction. */
|
||
if (GET_CODE (x) == NE)
|
||
fputs ("1", file);
|
||
|
||
else if (GET_CODE (x) == EQ)
|
||
fputs ("0", file);
|
||
|
||
else
|
||
fatal_insn ("Bad insn to frv_print_operand, 'e' modifier:", x);
|
||
break;
|
||
|
||
case 'F':
|
||
/* Print appropriate test for floating point branch false operation */
|
||
switch (GET_CODE (x))
|
||
{
|
||
default:
|
||
fatal_insn ("Bad insn to frv_print_operand, 'F' modifier:", x);
|
||
|
||
case EQ: fputs ("ne", file); break;
|
||
case NE: fputs ("eq", file); break;
|
||
case LT: fputs ("uge", file); break;
|
||
case LE: fputs ("ug", file); break;
|
||
case GT: fputs ("ule", file); break;
|
||
case GE: fputs ("ul", file); break;
|
||
}
|
||
break;
|
||
|
||
case 'f':
|
||
/* Print appropriate test for floating point branch true operation */
|
||
switch (GET_CODE (x))
|
||
{
|
||
default:
|
||
fatal_insn ("Bad insn to frv_print_operand, 'f' modifier:", x);
|
||
|
||
case EQ: fputs ("eq", file); break;
|
||
case NE: fputs ("ne", file); break;
|
||
case LT: fputs ("lt", file); break;
|
||
case LE: fputs ("le", file); break;
|
||
case GT: fputs ("gt", file); break;
|
||
case GE: fputs ("ge", file); break;
|
||
}
|
||
break;
|
||
|
||
case 'I':
|
||
/* Print 'i' if the operand is a constant, or is a memory reference that
|
||
adds a constant */
|
||
if (GET_CODE (x) == MEM)
|
||
x = ((GET_CODE (XEXP (x, 0)) == PLUS)
|
||
? XEXP (XEXP (x, 0), 1)
|
||
: XEXP (x, 0));
|
||
|
||
switch (GET_CODE (x))
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case CONST_INT:
|
||
case SYMBOL_REF:
|
||
case CONST:
|
||
fputs ("i", file);
|
||
break;
|
||
}
|
||
break;
|
||
|
||
case 'i':
|
||
/* For jump instructions, print 'i' if the operand is a constant or
|
||
is an expression that adds a constant */
|
||
if (GET_CODE (x) == CONST_INT)
|
||
fputs ("i", file);
|
||
|
||
else
|
||
{
|
||
if (GET_CODE (x) == CONST_INT
|
||
|| (GET_CODE (x) == PLUS
|
||
&& (GET_CODE (XEXP (x, 1)) == CONST_INT
|
||
|| GET_CODE (XEXP (x, 0)) == CONST_INT)))
|
||
fputs ("i", file);
|
||
}
|
||
break;
|
||
|
||
case 'L':
|
||
/* Print the lower register of a double word register pair */
|
||
if (GET_CODE (x) == REG)
|
||
fputs (reg_names[ REGNO (x)+1 ], file);
|
||
else
|
||
fatal_insn ("Bad insn to frv_print_operand, 'L' modifier:", x);
|
||
break;
|
||
|
||
/* case 'l': print a LABEL_REF */
|
||
|
||
case 'M':
|
||
case 'N':
|
||
/* Print a memory reference for ld/st/jmp, %N prints a memory reference
|
||
for the second word of double memory operations. */
|
||
offset = (code == 'M') ? 0 : UNITS_PER_WORD;
|
||
switch (GET_CODE (x))
|
||
{
|
||
default:
|
||
fatal_insn ("Bad insn to frv_print_operand, 'M/N' modifier:", x);
|
||
|
||
case MEM:
|
||
frv_print_operand_memory_reference (file, XEXP (x, 0), offset);
|
||
break;
|
||
|
||
case REG:
|
||
case SUBREG:
|
||
case CONST_INT:
|
||
case PLUS:
|
||
case SYMBOL_REF:
|
||
frv_print_operand_memory_reference (file, x, offset);
|
||
break;
|
||
}
|
||
break;
|
||
|
||
case 'O':
|
||
/* Print the opcode of a command. */
|
||
switch (GET_CODE (x))
|
||
{
|
||
default:
|
||
fatal_insn ("Bad insn to frv_print_operand, 'O' modifier:", x);
|
||
|
||
case PLUS: fputs ("add", file); break;
|
||
case MINUS: fputs ("sub", file); break;
|
||
case AND: fputs ("and", file); break;
|
||
case IOR: fputs ("or", file); break;
|
||
case XOR: fputs ("xor", file); break;
|
||
case ASHIFT: fputs ("sll", file); break;
|
||
case ASHIFTRT: fputs ("sra", file); break;
|
||
case LSHIFTRT: fputs ("srl", file); break;
|
||
}
|
||
break;
|
||
|
||
/* case 'n': negate and print a constant int */
|
||
|
||
case 'P':
|
||
/* Print PIC label using operand as the number. */
|
||
if (GET_CODE (x) != CONST_INT)
|
||
fatal_insn ("Bad insn to frv_print_operand, P modifier:", x);
|
||
|
||
fprintf (file, ".LCF%ld", (long)INTVAL (x));
|
||
break;
|
||
|
||
case 'U':
|
||
/* Print 'u' if the operand is a update load/store */
|
||
if (GET_CODE (x) == MEM && GET_CODE (XEXP (x, 0)) == PRE_MODIFY)
|
||
fputs ("u", file);
|
||
break;
|
||
|
||
case 'z':
|
||
/* If value is 0, print gr0, otherwise it must be a register */
|
||
if (GET_CODE (x) == CONST_INT && INTVAL (x) == 0)
|
||
fputs (reg_names[GPR_R0], file);
|
||
|
||
else if (GET_CODE (x) == REG)
|
||
fputs (reg_names [REGNO (x)], file);
|
||
|
||
else
|
||
fatal_insn ("Bad insn in frv_print_operand, z case", x);
|
||
break;
|
||
|
||
case 'x':
|
||
/* Print constant in hex */
|
||
if (GET_CODE (x) == CONST_INT || GET_CODE (x) == CONST_DOUBLE)
|
||
{
|
||
fprintf (file, "%s0x%.4lx", IMMEDIATE_PREFIX, (long) value);
|
||
break;
|
||
}
|
||
|
||
/* fall through */
|
||
|
||
case '\0':
|
||
if (GET_CODE (x) == REG)
|
||
fputs (reg_names [REGNO (x)], file);
|
||
|
||
else if (GET_CODE (x) == CONST_INT
|
||
|| GET_CODE (x) == CONST_DOUBLE)
|
||
fprintf (file, "%s%ld", IMMEDIATE_PREFIX, (long) value);
|
||
|
||
else if (GET_CODE (x) == MEM)
|
||
frv_print_operand_address (file, XEXP (x, 0));
|
||
|
||
else if (CONSTANT_ADDRESS_P (x))
|
||
frv_print_operand_address (file, x);
|
||
|
||
else
|
||
fatal_insn ("Bad insn in frv_print_operand, 0 case", x);
|
||
|
||
break;
|
||
|
||
default:
|
||
fatal_insn ("frv_print_operand: unknown code", x);
|
||
break;
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
/* A C statement (sans semicolon) for initializing the variable CUM for the
|
||
state at the beginning of the argument list. The variable has type
|
||
`CUMULATIVE_ARGS'. The value of FNTYPE is the tree node for the data type
|
||
of the function which will receive the args, or 0 if the args are to a
|
||
compiler support library function. The value of INDIRECT is nonzero when
|
||
processing an indirect call, for example a call through a function pointer.
|
||
The value of INDIRECT is zero for a call to an explicitly named function, a
|
||
library function call, or when `INIT_CUMULATIVE_ARGS' is used to find
|
||
arguments for the function being compiled.
|
||
|
||
When processing a call to a compiler support library function, LIBNAME
|
||
identifies which one. It is a `symbol_ref' rtx which contains the name of
|
||
the function, as a string. LIBNAME is 0 when an ordinary C function call is
|
||
being processed. Thus, each time this macro is called, either LIBNAME or
|
||
FNTYPE is nonzero, but never both of them at once. */
|
||
|
||
void
|
||
frv_init_cumulative_args (cum, fntype, libname, indirect, incoming)
|
||
CUMULATIVE_ARGS *cum;
|
||
tree fntype;
|
||
rtx libname;
|
||
int indirect;
|
||
int incoming;
|
||
{
|
||
*cum = FIRST_ARG_REGNUM;
|
||
|
||
if (TARGET_DEBUG_ARG)
|
||
{
|
||
fprintf (stderr, "\ninit_cumulative_args:");
|
||
if (indirect)
|
||
fputs (" indirect", stderr);
|
||
|
||
if (incoming)
|
||
fputs (" incoming", stderr);
|
||
|
||
if (fntype)
|
||
{
|
||
tree ret_type = TREE_TYPE (fntype);
|
||
fprintf (stderr, " return=%s,",
|
||
tree_code_name[ (int)TREE_CODE (ret_type) ]);
|
||
}
|
||
|
||
if (libname && GET_CODE (libname) == SYMBOL_REF)
|
||
fprintf (stderr, " libname=%s", XSTR (libname, 0));
|
||
|
||
if (cfun->returns_struct)
|
||
fprintf (stderr, " return-struct");
|
||
|
||
putc ('\n', stderr);
|
||
}
|
||
}
|
||
|
||
|
||
/* If defined, a C expression that gives the alignment boundary, in bits, of an
|
||
argument with the specified mode and type. If it is not defined,
|
||
`PARM_BOUNDARY' is used for all arguments. */
|
||
|
||
int
|
||
frv_function_arg_boundary (mode, type)
|
||
enum machine_mode mode ATTRIBUTE_UNUSED;
|
||
tree type ATTRIBUTE_UNUSED;
|
||
{
|
||
return BITS_PER_WORD;
|
||
}
|
||
|
||
|
||
/* A C expression that controls whether a function argument is passed in a
|
||
register, and which register.
|
||
|
||
The arguments are CUM, of type CUMULATIVE_ARGS, which summarizes (in a way
|
||
defined by INIT_CUMULATIVE_ARGS and FUNCTION_ARG_ADVANCE) all of the previous
|
||
arguments so far passed in registers; MODE, the machine mode of the argument;
|
||
TYPE, the data type of the argument as a tree node or 0 if that is not known
|
||
(which happens for C support library functions); and NAMED, which is 1 for an
|
||
ordinary argument and 0 for nameless arguments that correspond to `...' in the
|
||
called function's prototype.
|
||
|
||
The value of the expression should either be a `reg' RTX for the hard
|
||
register in which to pass the argument, or zero to pass the argument on the
|
||
stack.
|
||
|
||
For machines like the VAX and 68000, where normally all arguments are
|
||
pushed, zero suffices as a definition.
|
||
|
||
The usual way to make the ANSI library `stdarg.h' work on a machine where
|
||
some arguments are usually passed in registers, is to cause nameless
|
||
arguments to be passed on the stack instead. This is done by making
|
||
`FUNCTION_ARG' return 0 whenever NAMED is 0.
|
||
|
||
You may use the macro `MUST_PASS_IN_STACK (MODE, TYPE)' in the definition of
|
||
this macro to determine if this argument is of a type that must be passed in
|
||
the stack. If `REG_PARM_STACK_SPACE' is not defined and `FUNCTION_ARG'
|
||
returns nonzero for such an argument, the compiler will abort. If
|
||
`REG_PARM_STACK_SPACE' is defined, the argument will be computed in the
|
||
stack and then loaded into a register. */
|
||
|
||
rtx
|
||
frv_function_arg (cum, mode, type, named, incoming)
|
||
CUMULATIVE_ARGS *cum;
|
||
enum machine_mode mode;
|
||
tree type ATTRIBUTE_UNUSED;
|
||
int named;
|
||
int incoming ATTRIBUTE_UNUSED;
|
||
{
|
||
enum machine_mode xmode = (mode == BLKmode) ? SImode : mode;
|
||
int arg_num = *cum;
|
||
rtx ret;
|
||
const char *debstr;
|
||
|
||
/* Return a marker for use in the call instruction. */
|
||
if (xmode == VOIDmode)
|
||
{
|
||
ret = const0_rtx;
|
||
debstr = "<0>";
|
||
}
|
||
|
||
else if (arg_num <= LAST_ARG_REGNUM)
|
||
{
|
||
ret = gen_rtx (REG, xmode, arg_num);
|
||
debstr = reg_names[arg_num];
|
||
}
|
||
|
||
else
|
||
{
|
||
ret = NULL_RTX;
|
||
debstr = "memory";
|
||
}
|
||
|
||
if (TARGET_DEBUG_ARG)
|
||
fprintf (stderr,
|
||
"function_arg: words = %2d, mode = %4s, named = %d, size = %3d, arg = %s\n",
|
||
arg_num, GET_MODE_NAME (mode), named, GET_MODE_SIZE (mode), debstr);
|
||
|
||
return ret;
|
||
}
|
||
|
||
|
||
/* A C statement (sans semicolon) to update the summarizer variable CUM to
|
||
advance past an argument in the argument list. The values MODE, TYPE and
|
||
NAMED describe that argument. Once this is done, the variable CUM is
|
||
suitable for analyzing the *following* argument with `FUNCTION_ARG', etc.
|
||
|
||
This macro need not do anything if the argument in question was passed on
|
||
the stack. The compiler knows how to track the amount of stack space used
|
||
for arguments without any special help. */
|
||
|
||
void
|
||
frv_function_arg_advance (cum, mode, type, named)
|
||
CUMULATIVE_ARGS *cum;
|
||
enum machine_mode mode;
|
||
tree type ATTRIBUTE_UNUSED;
|
||
int named;
|
||
{
|
||
enum machine_mode xmode = (mode == BLKmode) ? SImode : mode;
|
||
int bytes = GET_MODE_SIZE (xmode);
|
||
int words = (bytes + UNITS_PER_WORD - 1) / UNITS_PER_WORD;
|
||
int arg_num = *cum;
|
||
|
||
*cum = arg_num + words;
|
||
|
||
if (TARGET_DEBUG_ARG)
|
||
fprintf (stderr,
|
||
"function_adv: words = %2d, mode = %4s, named = %d, size = %3d\n",
|
||
arg_num, GET_MODE_NAME (mode), named, words * UNITS_PER_WORD);
|
||
}
|
||
|
||
|
||
/* A C expression for the number of words, at the beginning of an argument,
|
||
must be put in registers. The value must be zero for arguments that are
|
||
passed entirely in registers or that are entirely pushed on the stack.
|
||
|
||
On some machines, certain arguments must be passed partially in registers
|
||
and partially in memory. On these machines, typically the first N words of
|
||
arguments are passed in registers, and the rest on the stack. If a
|
||
multi-word argument (a `double' or a structure) crosses that boundary, its
|
||
first few words must be passed in registers and the rest must be pushed.
|
||
This macro tells the compiler when this occurs, and how many of the words
|
||
should go in registers.
|
||
|
||
`FUNCTION_ARG' for these arguments should return the first register to be
|
||
used by the caller for this argument; likewise `FUNCTION_INCOMING_ARG', for
|
||
the called function. */
|
||
|
||
int
|
||
frv_function_arg_partial_nregs (cum, mode, type, named)
|
||
CUMULATIVE_ARGS *cum;
|
||
enum machine_mode mode;
|
||
tree type ATTRIBUTE_UNUSED;
|
||
int named ATTRIBUTE_UNUSED;
|
||
{
|
||
enum machine_mode xmode = (mode == BLKmode) ? SImode : mode;
|
||
int bytes = GET_MODE_SIZE (xmode);
|
||
int words = (bytes + UNITS_PER_WORD - 1) / UNITS_PER_WORD;
|
||
int arg_num = *cum;
|
||
int ret;
|
||
|
||
ret = ((arg_num <= LAST_ARG_REGNUM && arg_num + words > LAST_ARG_REGNUM+1)
|
||
? LAST_ARG_REGNUM - arg_num + 1
|
||
: 0);
|
||
|
||
if (TARGET_DEBUG_ARG && ret)
|
||
fprintf (stderr, "function_arg_partial_nregs: %d\n", ret);
|
||
|
||
return ret;
|
||
|
||
}
|
||
|
||
|
||
|
||
/* A C expression that indicates when an argument must be passed by reference.
|
||
If nonzero for an argument, a copy of that argument is made in memory and a
|
||
pointer to the argument is passed instead of the argument itself. The
|
||
pointer is passed in whatever way is appropriate for passing a pointer to
|
||
that type.
|
||
|
||
On machines where `REG_PARM_STACK_SPACE' is not defined, a suitable
|
||
definition of this macro might be
|
||
#define FUNCTION_ARG_PASS_BY_REFERENCE(CUM, MODE, TYPE, NAMED) \
|
||
MUST_PASS_IN_STACK (MODE, TYPE) */
|
||
|
||
int
|
||
frv_function_arg_pass_by_reference (cum, mode, type, named)
|
||
CUMULATIVE_ARGS *cum ATTRIBUTE_UNUSED;
|
||
enum machine_mode mode;
|
||
tree type;
|
||
int named ATTRIBUTE_UNUSED;
|
||
{
|
||
return MUST_PASS_IN_STACK (mode, type);
|
||
}
|
||
|
||
/* If defined, a C expression that indicates when it is the called function's
|
||
responsibility to make a copy of arguments passed by invisible reference.
|
||
Normally, the caller makes a copy and passes the address of the copy to the
|
||
routine being called. When FUNCTION_ARG_CALLEE_COPIES is defined and is
|
||
nonzero, the caller does not make a copy. Instead, it passes a pointer to
|
||
the "live" value. The called function must not modify this value. If it
|
||
can be determined that the value won't be modified, it need not make a copy;
|
||
otherwise a copy must be made. */
|
||
|
||
int
|
||
frv_function_arg_callee_copies (cum, mode, type, named)
|
||
CUMULATIVE_ARGS *cum ATTRIBUTE_UNUSED;
|
||
enum machine_mode mode ATTRIBUTE_UNUSED;
|
||
tree type ATTRIBUTE_UNUSED;
|
||
int named ATTRIBUTE_UNUSED;
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
/* If defined, a C expression that indicates when it is more desirable to keep
|
||
an argument passed by invisible reference as a reference, rather than
|
||
copying it to a pseudo register. */
|
||
|
||
int
|
||
frv_function_arg_keep_as_reference (cum, mode, type, named)
|
||
CUMULATIVE_ARGS *cum ATTRIBUTE_UNUSED;
|
||
enum machine_mode mode ATTRIBUTE_UNUSED;
|
||
tree type ATTRIBUTE_UNUSED;
|
||
int named ATTRIBUTE_UNUSED;
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
|
||
/* Return true if a register is ok to use as a base or index register. */
|
||
|
||
static FRV_INLINE int
|
||
frv_regno_ok_for_base_p (regno, strict_p)
|
||
int regno;
|
||
int strict_p;
|
||
{
|
||
if (GPR_P (regno))
|
||
return TRUE;
|
||
|
||
if (strict_p)
|
||
return (reg_renumber[regno] >= 0 && GPR_P (reg_renumber[regno]));
|
||
|
||
if (regno == ARG_POINTER_REGNUM)
|
||
return TRUE;
|
||
|
||
return (regno >= FIRST_PSEUDO_REGISTER);
|
||
}
|
||
|
||
|
||
/* A C compound statement with a conditional `goto LABEL;' executed if X (an
|
||
RTX) is a legitimate memory address on the target machine for a memory
|
||
operand of mode MODE.
|
||
|
||
It usually pays to define several simpler macros to serve as subroutines for
|
||
this one. Otherwise it may be too complicated to understand.
|
||
|
||
This macro must exist in two variants: a strict variant and a non-strict
|
||
one. The strict variant is used in the reload pass. It must be defined so
|
||
that any pseudo-register that has not been allocated a hard register is
|
||
considered a memory reference. In contexts where some kind of register is
|
||
required, a pseudo-register with no hard register must be rejected.
|
||
|
||
The non-strict variant is used in other passes. It must be defined to
|
||
accept all pseudo-registers in every context where some kind of register is
|
||
required.
|
||
|
||
Compiler source files that want to use the strict variant of this macro
|
||
define the macro `REG_OK_STRICT'. You should use an `#ifdef REG_OK_STRICT'
|
||
conditional to define the strict variant in that case and the non-strict
|
||
variant otherwise.
|
||
|
||
Subroutines to check for acceptable registers for various purposes (one for
|
||
base registers, one for index registers, and so on) are typically among the
|
||
subroutines used to define `GO_IF_LEGITIMATE_ADDRESS'. Then only these
|
||
subroutine macros need have two variants; the higher levels of macros may be
|
||
the same whether strict or not.
|
||
|
||
Normally, constant addresses which are the sum of a `symbol_ref' and an
|
||
integer are stored inside a `const' RTX to mark them as constant.
|
||
Therefore, there is no need to recognize such sums specifically as
|
||
legitimate addresses. Normally you would simply recognize any `const' as
|
||
legitimate.
|
||
|
||
Usually `PRINT_OPERAND_ADDRESS' is not prepared to handle constant sums that
|
||
are not marked with `const'. It assumes that a naked `plus' indicates
|
||
indexing. If so, then you *must* reject such naked constant sums as
|
||
illegitimate addresses, so that none of them will be given to
|
||
`PRINT_OPERAND_ADDRESS'.
|
||
|
||
On some machines, whether a symbolic address is legitimate depends on the
|
||
section that the address refers to. On these machines, define the macro
|
||
`ENCODE_SECTION_INFO' to store the information into the `symbol_ref', and
|
||
then check for it here. When you see a `const', you will have to look
|
||
inside it to find the `symbol_ref' in order to determine the section.
|
||
|
||
The best way to modify the name string is by adding text to the beginning,
|
||
with suitable punctuation to prevent any ambiguity. Allocate the new name
|
||
in `saveable_obstack'. You will have to modify `ASM_OUTPUT_LABELREF' to
|
||
remove and decode the added text and output the name accordingly, and define
|
||
`(* targetm.strip_name_encoding)' to access the original name string.
|
||
|
||
You can check the information stored here into the `symbol_ref' in the
|
||
definitions of the macros `GO_IF_LEGITIMATE_ADDRESS' and
|
||
`PRINT_OPERAND_ADDRESS'. */
|
||
|
||
int
|
||
frv_legitimate_address_p (mode, x, strict_p, condexec_p)
|
||
enum machine_mode mode;
|
||
rtx x;
|
||
int strict_p;
|
||
int condexec_p;
|
||
{
|
||
rtx x0, x1;
|
||
int ret = 0;
|
||
HOST_WIDE_INT value;
|
||
unsigned regno0;
|
||
|
||
switch (GET_CODE (x))
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case SUBREG:
|
||
x = SUBREG_REG (x);
|
||
if (GET_CODE (x) != REG)
|
||
break;
|
||
|
||
/* fall through */
|
||
|
||
case REG:
|
||
ret = frv_regno_ok_for_base_p (REGNO (x), strict_p);
|
||
break;
|
||
|
||
case PRE_MODIFY:
|
||
x0 = XEXP (x, 0);
|
||
x1 = XEXP (x, 1);
|
||
if (GET_CODE (x0) != REG
|
||
|| ! frv_regno_ok_for_base_p (REGNO (x0), strict_p)
|
||
|| GET_CODE (x1) != PLUS
|
||
|| ! rtx_equal_p (x0, XEXP (x1, 0))
|
||
|| GET_CODE (XEXP (x1, 1)) != REG
|
||
|| ! frv_regno_ok_for_base_p (REGNO (XEXP (x1, 1)), strict_p))
|
||
break;
|
||
|
||
ret = 1;
|
||
break;
|
||
|
||
case CONST_INT:
|
||
/* 12 bit immediate */
|
||
if (condexec_p)
|
||
ret = FALSE;
|
||
else
|
||
{
|
||
ret = IN_RANGE_P (INTVAL (x), -2048, 2047);
|
||
|
||
/* If we can't use load/store double operations, make sure we can
|
||
address the second word. */
|
||
if (ret && GET_MODE_SIZE (mode) > UNITS_PER_WORD)
|
||
ret = IN_RANGE_P (INTVAL (x) + GET_MODE_SIZE (mode) - 1,
|
||
-2048, 2047);
|
||
}
|
||
break;
|
||
|
||
case PLUS:
|
||
x0 = XEXP (x, 0);
|
||
x1 = XEXP (x, 1);
|
||
|
||
if (GET_CODE (x0) == SUBREG)
|
||
x0 = SUBREG_REG (x0);
|
||
|
||
if (GET_CODE (x0) != REG)
|
||
break;
|
||
|
||
regno0 = REGNO (x0);
|
||
if (!frv_regno_ok_for_base_p (regno0, strict_p))
|
||
break;
|
||
|
||
switch (GET_CODE (x1))
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case SUBREG:
|
||
x1 = SUBREG_REG (x1);
|
||
if (GET_CODE (x1) != REG)
|
||
break;
|
||
|
||
/* fall through */
|
||
|
||
case REG:
|
||
/* Do not allow reg+reg addressing for modes > 1 word if we can't depend
|
||
on having move double instructions */
|
||
if (GET_MODE_SIZE (mode) > UNITS_PER_WORD)
|
||
ret = FALSE;
|
||
else
|
||
ret = frv_regno_ok_for_base_p (REGNO (x1), strict_p);
|
||
break;
|
||
|
||
case CONST_INT:
|
||
/* 12 bit immediate */
|
||
if (condexec_p)
|
||
ret = FALSE;
|
||
else
|
||
{
|
||
value = INTVAL (x1);
|
||
ret = IN_RANGE_P (value, -2048, 2047);
|
||
|
||
/* If we can't use load/store double operations, make sure we can
|
||
address the second word. */
|
||
if (ret && GET_MODE_SIZE (mode) > UNITS_PER_WORD)
|
||
ret = IN_RANGE_P (value + GET_MODE_SIZE (mode) - 1, -2048, 2047);
|
||
}
|
||
break;
|
||
|
||
case SYMBOL_REF:
|
||
if (!condexec_p
|
||
&& regno0 == SDA_BASE_REG
|
||
&& symbol_ref_small_data_p (x1))
|
||
ret = TRUE;
|
||
break;
|
||
|
||
case CONST:
|
||
if (!condexec_p && regno0 == SDA_BASE_REG && const_small_data_p (x1))
|
||
ret = TRUE;
|
||
break;
|
||
|
||
}
|
||
break;
|
||
}
|
||
|
||
if (TARGET_DEBUG_ADDR)
|
||
{
|
||
fprintf (stderr, "\n========== GO_IF_LEGITIMATE_ADDRESS, mode = %s, result = %d, addresses are %sstrict%s\n",
|
||
GET_MODE_NAME (mode), ret, (strict_p) ? "" : "not ",
|
||
(condexec_p) ? ", inside conditional code" : "");
|
||
debug_rtx (x);
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
|
||
/* A C compound statement that attempts to replace X with a valid memory
|
||
address for an operand of mode MODE. WIN will be a C statement label
|
||
elsewhere in the code; the macro definition may use
|
||
|
||
GO_IF_LEGITIMATE_ADDRESS (MODE, X, WIN);
|
||
|
||
to avoid further processing if the address has become legitimate.
|
||
|
||
X will always be the result of a call to `break_out_memory_refs', and OLDX
|
||
will be the operand that was given to that function to produce X.
|
||
|
||
The code generated by this macro should not alter the substructure of X. If
|
||
it transforms X into a more legitimate form, it should assign X (which will
|
||
always be a C variable) a new value.
|
||
|
||
It is not necessary for this macro to come up with a legitimate address.
|
||
The compiler has standard ways of doing so in all cases. In fact, it is
|
||
safe for this macro to do nothing. But often a machine-dependent strategy
|
||
can generate better code. */
|
||
|
||
rtx
|
||
frv_legitimize_address (x, oldx, mode)
|
||
rtx x;
|
||
rtx oldx ATTRIBUTE_UNUSED;
|
||
enum machine_mode mode ATTRIBUTE_UNUSED;
|
||
{
|
||
rtx ret = NULL_RTX;
|
||
|
||
/* Don't try to legitimize addresses if we are not optimizing, since the
|
||
address we generate is not a general operand, and will horribly mess
|
||
things up when force_reg is called to try and put it in a register because
|
||
we aren't optimizing. */
|
||
if (optimize
|
||
&& ((GET_CODE (x) == SYMBOL_REF && symbol_ref_small_data_p (x))
|
||
|| (GET_CODE (x) == CONST && const_small_data_p (x))))
|
||
{
|
||
ret = gen_rtx_PLUS (Pmode, gen_rtx_REG (Pmode, SDA_BASE_REG), x);
|
||
if (flag_pic)
|
||
cfun->uses_pic_offset_table = TRUE;
|
||
}
|
||
|
||
if (TARGET_DEBUG_ADDR && ret != NULL_RTX)
|
||
{
|
||
fprintf (stderr, "\n========== LEGITIMIZE_ADDRESS, mode = %s, modified address\n",
|
||
GET_MODE_NAME (mode));
|
||
debug_rtx (ret);
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
/* Return 1 if operand is a valid FRV address. CONDEXEC_P is true if
|
||
the operand is used by a predicated instruction. */
|
||
|
||
static int
|
||
frv_legitimate_memory_operand (op, mode, condexec_p)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
int condexec_p;
|
||
{
|
||
return ((GET_MODE (op) == mode || mode == VOIDmode)
|
||
&& GET_CODE (op) == MEM
|
||
&& frv_legitimate_address_p (mode, XEXP (op, 0),
|
||
reload_completed, condexec_p));
|
||
}
|
||
|
||
|
||
/* Return 1 is OP is a memory operand, or will be turned into one by
|
||
reload. */
|
||
|
||
int frv_load_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (reload_in_progress)
|
||
{
|
||
rtx tmp = op;
|
||
if (GET_CODE (tmp) == SUBREG)
|
||
tmp = SUBREG_REG (tmp);
|
||
if (GET_CODE (tmp) == REG
|
||
&& REGNO (tmp) >= FIRST_PSEUDO_REGISTER)
|
||
op = reg_equiv_memory_loc[REGNO (tmp)];
|
||
}
|
||
|
||
return op && memory_operand (op, mode);
|
||
}
|
||
|
||
|
||
/* Return 1 if operand is a GPR register or a FPR register. */
|
||
|
||
int gpr_or_fpr_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
int regno;
|
||
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) == SUBREG)
|
||
{
|
||
if (GET_CODE (SUBREG_REG (op)) != REG)
|
||
return register_operand (op, mode);
|
||
|
||
op = SUBREG_REG (op);
|
||
}
|
||
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
regno = REGNO (op);
|
||
if (GPR_P (regno) || FPR_P (regno) || regno >= FIRST_PSEUDO_REGISTER)
|
||
return TRUE;
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/* Return 1 if operand is a GPR register or 12 bit signed immediate. */
|
||
|
||
int gpr_or_int12_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
if (GET_CODE (op) == CONST_INT)
|
||
return IN_RANGE_P (INTVAL (op), -2048, 2047);
|
||
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) == SUBREG)
|
||
{
|
||
if (GET_CODE (SUBREG_REG (op)) != REG)
|
||
return register_operand (op, mode);
|
||
|
||
op = SUBREG_REG (op);
|
||
}
|
||
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
return GPR_OR_PSEUDO_P (REGNO (op));
|
||
}
|
||
|
||
/* Return 1 if operand is a GPR register, or a FPR register, or a 12 bit
|
||
signed immediate. */
|
||
|
||
int gpr_fpr_or_int12_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
int regno;
|
||
|
||
if (GET_CODE (op) == CONST_INT)
|
||
return IN_RANGE_P (INTVAL (op), -2048, 2047);
|
||
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) == SUBREG)
|
||
{
|
||
if (GET_CODE (SUBREG_REG (op)) != REG)
|
||
return register_operand (op, mode);
|
||
|
||
op = SUBREG_REG (op);
|
||
}
|
||
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
regno = REGNO (op);
|
||
if (GPR_P (regno) || FPR_P (regno) || regno >= FIRST_PSEUDO_REGISTER)
|
||
return TRUE;
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/* Return 1 if operand is a register or 6 bit signed immediate. */
|
||
|
||
int fpr_or_int6_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
if (GET_CODE (op) == CONST_INT)
|
||
return IN_RANGE_P (INTVAL (op), -32, 31);
|
||
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) == SUBREG)
|
||
{
|
||
if (GET_CODE (SUBREG_REG (op)) != REG)
|
||
return register_operand (op, mode);
|
||
|
||
op = SUBREG_REG (op);
|
||
}
|
||
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
return FPR_OR_PSEUDO_P (REGNO (op));
|
||
}
|
||
|
||
/* Return 1 if operand is a register or 10 bit signed immediate. */
|
||
|
||
int gpr_or_int10_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
if (GET_CODE (op) == CONST_INT)
|
||
return IN_RANGE_P (INTVAL (op), -512, 511);
|
||
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) == SUBREG)
|
||
{
|
||
if (GET_CODE (SUBREG_REG (op)) != REG)
|
||
return register_operand (op, mode);
|
||
|
||
op = SUBREG_REG (op);
|
||
}
|
||
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
return GPR_OR_PSEUDO_P (REGNO (op));
|
||
}
|
||
|
||
/* Return 1 if operand is a register or an integer immediate. */
|
||
|
||
int gpr_or_int_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
if (GET_CODE (op) == CONST_INT)
|
||
return TRUE;
|
||
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) == SUBREG)
|
||
{
|
||
if (GET_CODE (SUBREG_REG (op)) != REG)
|
||
return register_operand (op, mode);
|
||
|
||
op = SUBREG_REG (op);
|
||
}
|
||
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
return GPR_OR_PSEUDO_P (REGNO (op));
|
||
}
|
||
|
||
/* Return 1 if operand is a 12 bit signed immediate. */
|
||
|
||
int int12_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode ATTRIBUTE_UNUSED;
|
||
{
|
||
if (GET_CODE (op) != CONST_INT)
|
||
return FALSE;
|
||
|
||
return IN_RANGE_P (INTVAL (op), -2048, 2047);
|
||
}
|
||
|
||
/* Return 1 if operand is a 6 bit signed immediate. */
|
||
|
||
int int6_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode ATTRIBUTE_UNUSED;
|
||
{
|
||
if (GET_CODE (op) != CONST_INT)
|
||
return FALSE;
|
||
|
||
return IN_RANGE_P (INTVAL (op), -32, 31);
|
||
}
|
||
|
||
/* Return 1 if operand is a 5 bit signed immediate. */
|
||
|
||
int int5_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode ATTRIBUTE_UNUSED;
|
||
{
|
||
return GET_CODE (op) == CONST_INT && IN_RANGE_P (INTVAL (op), -16, 15);
|
||
}
|
||
|
||
/* Return 1 if operand is a 5 bit unsigned immediate. */
|
||
|
||
int uint5_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode ATTRIBUTE_UNUSED;
|
||
{
|
||
return GET_CODE (op) == CONST_INT && IN_RANGE_P (INTVAL (op), 0, 31);
|
||
}
|
||
|
||
/* Return 1 if operand is a 4 bit unsigned immediate. */
|
||
|
||
int uint4_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode ATTRIBUTE_UNUSED;
|
||
{
|
||
return GET_CODE (op) == CONST_INT && IN_RANGE_P (INTVAL (op), 0, 15);
|
||
}
|
||
|
||
/* Return 1 if operand is a 1 bit unsigned immediate (0 or 1). */
|
||
|
||
int uint1_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode ATTRIBUTE_UNUSED;
|
||
{
|
||
return GET_CODE (op) == CONST_INT && IN_RANGE_P (INTVAL (op), 0, 1);
|
||
}
|
||
|
||
/* Return 1 if operand is an integer constant that takes 2 instructions
|
||
to load up and can be split into sethi/setlo instructions.. */
|
||
|
||
int int_2word_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode ATTRIBUTE_UNUSED;
|
||
{
|
||
HOST_WIDE_INT value;
|
||
REAL_VALUE_TYPE rv;
|
||
long l;
|
||
|
||
switch (GET_CODE (op))
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case LABEL_REF:
|
||
return (flag_pic == 0);
|
||
|
||
case CONST:
|
||
/* small data references are already 1 word */
|
||
return (flag_pic == 0) && (! const_small_data_p (op));
|
||
|
||
case SYMBOL_REF:
|
||
/* small data references are already 1 word */
|
||
return (flag_pic == 0) && (! symbol_ref_small_data_p (op));
|
||
|
||
case CONST_INT:
|
||
return ! IN_RANGE_P (INTVAL (op), -32768, 32767);
|
||
|
||
case CONST_DOUBLE:
|
||
if (GET_MODE (op) == SFmode)
|
||
{
|
||
REAL_VALUE_FROM_CONST_DOUBLE (rv, op);
|
||
REAL_VALUE_TO_TARGET_SINGLE (rv, l);
|
||
value = l;
|
||
return ! IN_RANGE_P (value, -32768, 32767);
|
||
}
|
||
else if (GET_MODE (op) == VOIDmode)
|
||
{
|
||
value = CONST_DOUBLE_LOW (op);
|
||
return ! IN_RANGE_P (value, -32768, 32767);
|
||
}
|
||
break;
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/* Return 1 if operand is the pic address register. */
|
||
int
|
||
pic_register_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode ATTRIBUTE_UNUSED;
|
||
{
|
||
if (! flag_pic)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
if (REGNO (op) != PIC_REGNO)
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* Return 1 if operand is a symbolic reference when a PIC option is specified
|
||
that takes 3 seperate instructions to form. */
|
||
|
||
int pic_symbolic_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode ATTRIBUTE_UNUSED;
|
||
{
|
||
if (! flag_pic)
|
||
return FALSE;
|
||
|
||
switch (GET_CODE (op))
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case LABEL_REF:
|
||
return TRUE;
|
||
|
||
case SYMBOL_REF:
|
||
/* small data references are already 1 word */
|
||
return ! symbol_ref_small_data_p (op);
|
||
|
||
case CONST:
|
||
/* small data references are already 1 word */
|
||
return ! const_small_data_p (op);
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/* Return 1 if operand is the small data register. */
|
||
int
|
||
small_data_register_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode ATTRIBUTE_UNUSED;
|
||
{
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
if (REGNO (op) != SDA_BASE_REG)
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* Return 1 if operand is a symbolic reference to a small data area static or
|
||
global object. */
|
||
|
||
int small_data_symbolic_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode ATTRIBUTE_UNUSED;
|
||
{
|
||
switch (GET_CODE (op))
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case CONST:
|
||
return const_small_data_p (op);
|
||
|
||
case SYMBOL_REF:
|
||
return symbol_ref_small_data_p (op);
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/* Return 1 if operand is a 16 bit unsigned immediate */
|
||
|
||
int uint16_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode ATTRIBUTE_UNUSED;
|
||
{
|
||
if (GET_CODE (op) != CONST_INT)
|
||
return FALSE;
|
||
|
||
return IN_RANGE_P (INTVAL (op), 0, 0xffff);
|
||
}
|
||
|
||
/* Return 1 if operand is an integer constant with the bottom 16 bits clear */
|
||
|
||
int upper_int16_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode ATTRIBUTE_UNUSED;
|
||
{
|
||
if (GET_CODE (op) != CONST_INT)
|
||
return FALSE;
|
||
|
||
return ((INTVAL (op) & 0xffff) == 0);
|
||
}
|
||
|
||
/* Return true if operand is a GPR register. */
|
||
|
||
int
|
||
integer_register_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) == SUBREG)
|
||
{
|
||
if (GET_CODE (SUBREG_REG (op)) != REG)
|
||
return register_operand (op, mode);
|
||
|
||
op = SUBREG_REG (op);
|
||
}
|
||
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
return GPR_OR_PSEUDO_P (REGNO (op));
|
||
}
|
||
|
||
/* Return true if operand is a GPR register. Do not allow SUBREG's
|
||
here, in order to prevent a combine bug. */
|
||
|
||
int
|
||
gpr_no_subreg_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
return GPR_OR_PSEUDO_P (REGNO (op));
|
||
}
|
||
|
||
/* Return true if operand is a FPR register. */
|
||
|
||
int
|
||
fpr_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) == SUBREG)
|
||
{
|
||
if (GET_CODE (SUBREG_REG (op)) != REG)
|
||
return register_operand (op, mode);
|
||
|
||
op = SUBREG_REG (op);
|
||
}
|
||
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
return FPR_OR_PSEUDO_P (REGNO (op));
|
||
}
|
||
|
||
/* Return true if operand is an even GPR or FPR register. */
|
||
|
||
int
|
||
even_reg_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
int regno;
|
||
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) == SUBREG)
|
||
{
|
||
if (GET_CODE (SUBREG_REG (op)) != REG)
|
||
return register_operand (op, mode);
|
||
|
||
op = SUBREG_REG (op);
|
||
}
|
||
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
regno = REGNO (op);
|
||
if (regno >= FIRST_PSEUDO_REGISTER)
|
||
return TRUE;
|
||
|
||
if (GPR_P (regno))
|
||
return (((regno - GPR_FIRST) & 1) == 0);
|
||
|
||
if (FPR_P (regno))
|
||
return (((regno - FPR_FIRST) & 1) == 0);
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/* Return true if operand is an odd GPR register. */
|
||
|
||
int
|
||
odd_reg_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
int regno;
|
||
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) == SUBREG)
|
||
{
|
||
if (GET_CODE (SUBREG_REG (op)) != REG)
|
||
return register_operand (op, mode);
|
||
|
||
op = SUBREG_REG (op);
|
||
}
|
||
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
regno = REGNO (op);
|
||
/* assume that reload will give us an even register */
|
||
if (regno >= FIRST_PSEUDO_REGISTER)
|
||
return FALSE;
|
||
|
||
if (GPR_P (regno))
|
||
return (((regno - GPR_FIRST) & 1) != 0);
|
||
|
||
if (FPR_P (regno))
|
||
return (((regno - FPR_FIRST) & 1) != 0);
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/* Return true if operand is an even GPR register. */
|
||
|
||
int
|
||
even_gpr_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
int regno;
|
||
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) == SUBREG)
|
||
{
|
||
if (GET_CODE (SUBREG_REG (op)) != REG)
|
||
return register_operand (op, mode);
|
||
|
||
op = SUBREG_REG (op);
|
||
}
|
||
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
regno = REGNO (op);
|
||
if (regno >= FIRST_PSEUDO_REGISTER)
|
||
return TRUE;
|
||
|
||
if (! GPR_P (regno))
|
||
return FALSE;
|
||
|
||
return (((regno - GPR_FIRST) & 1) == 0);
|
||
}
|
||
|
||
/* Return true if operand is an odd GPR register. */
|
||
|
||
int
|
||
odd_gpr_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
int regno;
|
||
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) == SUBREG)
|
||
{
|
||
if (GET_CODE (SUBREG_REG (op)) != REG)
|
||
return register_operand (op, mode);
|
||
|
||
op = SUBREG_REG (op);
|
||
}
|
||
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
regno = REGNO (op);
|
||
/* assume that reload will give us an even register */
|
||
if (regno >= FIRST_PSEUDO_REGISTER)
|
||
return FALSE;
|
||
|
||
if (! GPR_P (regno))
|
||
return FALSE;
|
||
|
||
return (((regno - GPR_FIRST) & 1) != 0);
|
||
}
|
||
|
||
/* Return true if operand is a quad aligned FPR register. */
|
||
|
||
int
|
||
quad_fpr_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
int regno;
|
||
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) == SUBREG)
|
||
{
|
||
if (GET_CODE (SUBREG_REG (op)) != REG)
|
||
return register_operand (op, mode);
|
||
|
||
op = SUBREG_REG (op);
|
||
}
|
||
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
regno = REGNO (op);
|
||
if (regno >= FIRST_PSEUDO_REGISTER)
|
||
return TRUE;
|
||
|
||
if (! FPR_P (regno))
|
||
return FALSE;
|
||
|
||
return (((regno - FPR_FIRST) & 3) == 0);
|
||
}
|
||
|
||
/* Return true if operand is an even FPR register. */
|
||
|
||
int
|
||
even_fpr_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
int regno;
|
||
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) == SUBREG)
|
||
{
|
||
if (GET_CODE (SUBREG_REG (op)) != REG)
|
||
return register_operand (op, mode);
|
||
|
||
op = SUBREG_REG (op);
|
||
}
|
||
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
regno = REGNO (op);
|
||
if (regno >= FIRST_PSEUDO_REGISTER)
|
||
return TRUE;
|
||
|
||
if (! FPR_P (regno))
|
||
return FALSE;
|
||
|
||
return (((regno - FPR_FIRST) & 1) == 0);
|
||
}
|
||
|
||
/* Return true if operand is an odd FPR register. */
|
||
|
||
int
|
||
odd_fpr_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
int regno;
|
||
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) == SUBREG)
|
||
{
|
||
if (GET_CODE (SUBREG_REG (op)) != REG)
|
||
return register_operand (op, mode);
|
||
|
||
op = SUBREG_REG (op);
|
||
}
|
||
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
regno = REGNO (op);
|
||
/* assume that reload will give us an even register */
|
||
if (regno >= FIRST_PSEUDO_REGISTER)
|
||
return FALSE;
|
||
|
||
if (! FPR_P (regno))
|
||
return FALSE;
|
||
|
||
return (((regno - FPR_FIRST) & 1) != 0);
|
||
}
|
||
|
||
/* Return true if operand is a 2 word memory address that can be loaded in one
|
||
instruction to load or store. We assume the stack and frame pointers are
|
||
suitably aligned, and variables in the small data area. FIXME -- at some we
|
||
should recognize other globals and statics. We can't assume that any old
|
||
pointer is aligned, given that arguments could be passed on an odd word on
|
||
the stack and the address taken and passed through to another function. */
|
||
|
||
int
|
||
dbl_memory_one_insn_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
rtx addr;
|
||
rtx addr_reg;
|
||
|
||
if (! TARGET_DWORD)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) != MEM)
|
||
return FALSE;
|
||
|
||
if (mode != VOIDmode && GET_MODE_SIZE (mode) != 2*UNITS_PER_WORD)
|
||
return FALSE;
|
||
|
||
addr = XEXP (op, 0);
|
||
if (GET_CODE (addr) == REG)
|
||
addr_reg = addr;
|
||
|
||
else if (GET_CODE (addr) == PLUS)
|
||
{
|
||
rtx addr0 = XEXP (addr, 0);
|
||
rtx addr1 = XEXP (addr, 1);
|
||
|
||
if (GET_CODE (addr0) != REG)
|
||
return FALSE;
|
||
|
||
if (plus_small_data_p (addr0, addr1))
|
||
return TRUE;
|
||
|
||
if (GET_CODE (addr1) != CONST_INT)
|
||
return FALSE;
|
||
|
||
if ((INTVAL (addr1) & 7) != 0)
|
||
return FALSE;
|
||
|
||
addr_reg = addr0;
|
||
}
|
||
|
||
else
|
||
return FALSE;
|
||
|
||
if (addr_reg == frame_pointer_rtx || addr_reg == stack_pointer_rtx)
|
||
return TRUE;
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/* Return true if operand is a 2 word memory address that needs to
|
||
use two instructions to load or store. */
|
||
|
||
int
|
||
dbl_memory_two_insn_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
if (GET_CODE (op) != MEM)
|
||
return FALSE;
|
||
|
||
if (mode != VOIDmode && GET_MODE_SIZE (mode) != 2*UNITS_PER_WORD)
|
||
return FALSE;
|
||
|
||
if (! TARGET_DWORD)
|
||
return TRUE;
|
||
|
||
return ! dbl_memory_one_insn_operand (op, mode);
|
||
}
|
||
|
||
/* Return true if operand is something that can be an output for a move
|
||
operation. */
|
||
|
||
int
|
||
move_destination_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
rtx subreg;
|
||
enum rtx_code code;
|
||
|
||
switch (GET_CODE (op))
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case SUBREG:
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
subreg = SUBREG_REG (op);
|
||
code = GET_CODE (subreg);
|
||
if (code == MEM)
|
||
return frv_legitimate_address_p (mode, XEXP (subreg, 0),
|
||
reload_completed, FALSE);
|
||
|
||
return (code == REG);
|
||
|
||
case REG:
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
|
||
case MEM:
|
||
if (GET_CODE (XEXP (op, 0)) == ADDRESSOF)
|
||
return TRUE;
|
||
|
||
return frv_legitimate_memory_operand (op, mode, FALSE);
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/* Return true if operand is something that can be an input for a move
|
||
operation. */
|
||
|
||
int
|
||
move_source_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
rtx subreg;
|
||
enum rtx_code code;
|
||
|
||
switch (GET_CODE (op))
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case CONST_INT:
|
||
case CONST_DOUBLE:
|
||
case SYMBOL_REF:
|
||
case LABEL_REF:
|
||
case CONST:
|
||
return immediate_operand (op, mode);
|
||
|
||
case SUBREG:
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
subreg = SUBREG_REG (op);
|
||
code = GET_CODE (subreg);
|
||
if (code == MEM)
|
||
return frv_legitimate_address_p (mode, XEXP (subreg, 0),
|
||
reload_completed, FALSE);
|
||
|
||
return (code == REG);
|
||
|
||
case REG:
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
|
||
case MEM:
|
||
if (GET_CODE (XEXP (op, 0)) == ADDRESSOF)
|
||
return TRUE;
|
||
|
||
return frv_legitimate_memory_operand (op, mode, FALSE);
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/* Return true if operand is something that can be an output for a conditional
|
||
move operation. */
|
||
|
||
int
|
||
condexec_dest_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
rtx subreg;
|
||
enum rtx_code code;
|
||
|
||
switch (GET_CODE (op))
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case SUBREG:
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
subreg = SUBREG_REG (op);
|
||
code = GET_CODE (subreg);
|
||
if (code == MEM)
|
||
return frv_legitimate_address_p (mode, XEXP (subreg, 0),
|
||
reload_completed, TRUE);
|
||
|
||
return (code == REG);
|
||
|
||
case REG:
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
|
||
case MEM:
|
||
if (GET_CODE (XEXP (op, 0)) == ADDRESSOF)
|
||
return TRUE;
|
||
|
||
return frv_legitimate_memory_operand (op, mode, TRUE);
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/* Return true if operand is something that can be an input for a conditional
|
||
move operation. */
|
||
|
||
int
|
||
condexec_source_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
rtx subreg;
|
||
enum rtx_code code;
|
||
|
||
switch (GET_CODE (op))
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case CONST_INT:
|
||
case CONST_DOUBLE:
|
||
return ZERO_P (op);
|
||
|
||
case SUBREG:
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
subreg = SUBREG_REG (op);
|
||
code = GET_CODE (subreg);
|
||
if (code == MEM)
|
||
return frv_legitimate_address_p (mode, XEXP (subreg, 0),
|
||
reload_completed, TRUE);
|
||
|
||
return (code == REG);
|
||
|
||
case REG:
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
|
||
case MEM:
|
||
if (GET_CODE (XEXP (op, 0)) == ADDRESSOF)
|
||
return TRUE;
|
||
|
||
return frv_legitimate_memory_operand (op, mode, TRUE);
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/* Return true if operand is a register of any flavor or a 0 of the
|
||
appropriate type. */
|
||
|
||
int
|
||
reg_or_0_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
switch (GET_CODE (op))
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case REG:
|
||
case SUBREG:
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
return register_operand (op, mode);
|
||
|
||
case CONST_INT:
|
||
case CONST_DOUBLE:
|
||
return ZERO_P (op);
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/* Return true if operand is the link register */
|
||
|
||
int
|
||
lr_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (REGNO (op) != LR_REGNO && REGNO (op) < FIRST_PSEUDO_REGISTER)
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* Return true if operand is a gpr register or a valid memory operation. */
|
||
|
||
int
|
||
gpr_or_memory_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
return (integer_register_operand (op, mode)
|
||
|| frv_legitimate_memory_operand (op, mode, FALSE));
|
||
}
|
||
|
||
/* Return true if operand is a fpr register or a valid memory operation. */
|
||
|
||
int
|
||
fpr_or_memory_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
return (fpr_operand (op, mode)
|
||
|| frv_legitimate_memory_operand (op, mode, FALSE));
|
||
}
|
||
|
||
/* Return true if operand is an icc register */
|
||
|
||
int
|
||
icc_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
int regno;
|
||
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
regno = REGNO (op);
|
||
return ICC_OR_PSEUDO_P (regno);
|
||
}
|
||
|
||
/* Return true if operand is an fcc register */
|
||
|
||
int
|
||
fcc_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
int regno;
|
||
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
regno = REGNO (op);
|
||
return FCC_OR_PSEUDO_P (regno);
|
||
}
|
||
|
||
/* Return true if operand is either an fcc or icc register */
|
||
|
||
int
|
||
cc_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
int regno;
|
||
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
regno = REGNO (op);
|
||
if (CC_OR_PSEUDO_P (regno))
|
||
return TRUE;
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/* Return true if operand is an integer CCR register */
|
||
|
||
int
|
||
icr_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
int regno;
|
||
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
regno = REGNO (op);
|
||
return ICR_OR_PSEUDO_P (regno);
|
||
}
|
||
|
||
/* Return true if operand is an fcc register */
|
||
|
||
int
|
||
fcr_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
int regno;
|
||
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
regno = REGNO (op);
|
||
return FCR_OR_PSEUDO_P (regno);
|
||
}
|
||
|
||
/* Return true if operand is either an fcc or icc register */
|
||
|
||
int
|
||
cr_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
int regno;
|
||
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
regno = REGNO (op);
|
||
if (CR_OR_PSEUDO_P (regno))
|
||
return TRUE;
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/* Return true if operand is a memory reference suitable for a call. */
|
||
|
||
int
|
||
call_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
if (GET_MODE (op) != mode && mode != VOIDmode && GET_CODE (op) != CONST_INT)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) == SYMBOL_REF)
|
||
return TRUE;
|
||
|
||
/* Note this doesn't allow reg+reg or reg+imm12 addressing (which should
|
||
never occur anyway), but prevents reload from not handling the case
|
||
properly of a call through a pointer on a function that calls
|
||
vfork/setjmp, etc. due to the need to flush all of the registers to stack. */
|
||
return gpr_or_int12_operand (op, mode);
|
||
}
|
||
|
||
/* Return true if operator is an kind of relational operator */
|
||
|
||
int
|
||
relational_operator (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
rtx op0;
|
||
rtx op1;
|
||
int regno;
|
||
|
||
if (mode != VOIDmode && mode != GET_MODE (op))
|
||
return FALSE;
|
||
|
||
switch (GET_CODE (op))
|
||
{
|
||
default:
|
||
return FALSE;
|
||
|
||
case EQ:
|
||
case NE:
|
||
case LE:
|
||
case LT:
|
||
case GE:
|
||
case GT:
|
||
case LEU:
|
||
case LTU:
|
||
case GEU:
|
||
case GTU:
|
||
break;
|
||
}
|
||
|
||
op1 = XEXP (op, 1);
|
||
if (op1 != const0_rtx)
|
||
return FALSE;
|
||
|
||
op0 = XEXP (op, 0);
|
||
if (GET_CODE (op0) != REG)
|
||
return FALSE;
|
||
|
||
regno = REGNO (op0);
|
||
switch (GET_MODE (op0))
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case CCmode:
|
||
case CC_UNSmode:
|
||
return ICC_OR_PSEUDO_P (regno);
|
||
|
||
case CC_FPmode:
|
||
return FCC_OR_PSEUDO_P (regno);
|
||
|
||
case CC_CCRmode:
|
||
return CR_OR_PSEUDO_P (regno);
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/* Return true if operator is a signed integer relational operator */
|
||
|
||
int
|
||
signed_relational_operator (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
rtx op0;
|
||
rtx op1;
|
||
int regno;
|
||
|
||
if (mode != VOIDmode && mode != GET_MODE (op))
|
||
return FALSE;
|
||
|
||
switch (GET_CODE (op))
|
||
{
|
||
default:
|
||
return FALSE;
|
||
|
||
case EQ:
|
||
case NE:
|
||
case LE:
|
||
case LT:
|
||
case GE:
|
||
case GT:
|
||
break;
|
||
}
|
||
|
||
op1 = XEXP (op, 1);
|
||
if (op1 != const0_rtx)
|
||
return FALSE;
|
||
|
||
op0 = XEXP (op, 0);
|
||
if (GET_CODE (op0) != REG)
|
||
return FALSE;
|
||
|
||
regno = REGNO (op0);
|
||
if (GET_MODE (op0) == CCmode && ICC_OR_PSEUDO_P (regno))
|
||
return TRUE;
|
||
|
||
if (GET_MODE (op0) == CC_CCRmode && CR_OR_PSEUDO_P (regno))
|
||
return TRUE;
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/* Return true if operator is a signed integer relational operator */
|
||
|
||
int
|
||
unsigned_relational_operator (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
rtx op0;
|
||
rtx op1;
|
||
int regno;
|
||
|
||
if (mode != VOIDmode && mode != GET_MODE (op))
|
||
return FALSE;
|
||
|
||
switch (GET_CODE (op))
|
||
{
|
||
default:
|
||
return FALSE;
|
||
|
||
case LEU:
|
||
case LTU:
|
||
case GEU:
|
||
case GTU:
|
||
break;
|
||
}
|
||
|
||
op1 = XEXP (op, 1);
|
||
if (op1 != const0_rtx)
|
||
return FALSE;
|
||
|
||
op0 = XEXP (op, 0);
|
||
if (GET_CODE (op0) != REG)
|
||
return FALSE;
|
||
|
||
regno = REGNO (op0);
|
||
if (GET_MODE (op0) == CC_UNSmode && ICC_OR_PSEUDO_P (regno))
|
||
return TRUE;
|
||
|
||
if (GET_MODE (op0) == CC_CCRmode && CR_OR_PSEUDO_P (regno))
|
||
return TRUE;
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/* Return true if operator is a floating point relational operator */
|
||
|
||
int
|
||
float_relational_operator (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
rtx op0;
|
||
rtx op1;
|
||
int regno;
|
||
|
||
if (mode != VOIDmode && mode != GET_MODE (op))
|
||
return FALSE;
|
||
|
||
switch (GET_CODE (op))
|
||
{
|
||
default:
|
||
return FALSE;
|
||
|
||
case EQ: case NE:
|
||
case LE: case LT:
|
||
case GE: case GT:
|
||
#if 0
|
||
case UEQ: case UNE:
|
||
case ULE: case ULT:
|
||
case UGE: case UGT:
|
||
case ORDERED:
|
||
case UNORDERED:
|
||
#endif
|
||
break;
|
||
}
|
||
|
||
op1 = XEXP (op, 1);
|
||
if (op1 != const0_rtx)
|
||
return FALSE;
|
||
|
||
op0 = XEXP (op, 0);
|
||
if (GET_CODE (op0) != REG)
|
||
return FALSE;
|
||
|
||
regno = REGNO (op0);
|
||
if (GET_MODE (op0) == CC_FPmode && FCC_OR_PSEUDO_P (regno))
|
||
return TRUE;
|
||
|
||
if (GET_MODE (op0) == CC_CCRmode && CR_OR_PSEUDO_P (regno))
|
||
return TRUE;
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/* Return true if operator is EQ/NE of a conditional execution register. */
|
||
|
||
int
|
||
ccr_eqne_operator (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
enum machine_mode op_mode = GET_MODE (op);
|
||
rtx op0;
|
||
rtx op1;
|
||
int regno;
|
||
|
||
if (mode != VOIDmode && op_mode != mode)
|
||
return FALSE;
|
||
|
||
switch (GET_CODE (op))
|
||
{
|
||
default:
|
||
return FALSE;
|
||
|
||
case EQ:
|
||
case NE:
|
||
break;
|
||
}
|
||
|
||
op1 = XEXP (op, 1);
|
||
if (op1 != const0_rtx)
|
||
return FALSE;
|
||
|
||
op0 = XEXP (op, 0);
|
||
if (GET_CODE (op0) != REG)
|
||
return FALSE;
|
||
|
||
regno = REGNO (op0);
|
||
if (op_mode == CC_CCRmode && CR_OR_PSEUDO_P (regno))
|
||
return TRUE;
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/* Return true if operator is a minimum or maximum operator (both signed and
|
||
unsigned). */
|
||
|
||
int
|
||
minmax_operator (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
if (mode != VOIDmode && mode != GET_MODE (op))
|
||
return FALSE;
|
||
|
||
switch (GET_CODE (op))
|
||
{
|
||
default:
|
||
return FALSE;
|
||
|
||
case SMIN:
|
||
case SMAX:
|
||
case UMIN:
|
||
case UMAX:
|
||
break;
|
||
}
|
||
|
||
if (! integer_register_operand (XEXP (op, 0), mode))
|
||
return FALSE;
|
||
|
||
if (! gpr_or_int10_operand (XEXP (op, 1), mode))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* Return true if operator is an integer binary operator that can executed
|
||
conditionally and takes 1 cycle. */
|
||
|
||
int
|
||
condexec_si_binary_operator (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
enum machine_mode op_mode = GET_MODE (op);
|
||
|
||
if (mode != VOIDmode && op_mode != mode)
|
||
return FALSE;
|
||
|
||
switch (GET_CODE (op))
|
||
{
|
||
default:
|
||
return FALSE;
|
||
|
||
case PLUS:
|
||
case MINUS:
|
||
case AND:
|
||
case IOR:
|
||
case XOR:
|
||
case ASHIFT:
|
||
case ASHIFTRT:
|
||
case LSHIFTRT:
|
||
return TRUE;
|
||
}
|
||
}
|
||
|
||
/* Return true if operator is an integer binary operator that can be
|
||
executed conditionally by a media instruction. */
|
||
|
||
int
|
||
condexec_si_media_operator (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
enum machine_mode op_mode = GET_MODE (op);
|
||
|
||
if (mode != VOIDmode && op_mode != mode)
|
||
return FALSE;
|
||
|
||
switch (GET_CODE (op))
|
||
{
|
||
default:
|
||
return FALSE;
|
||
|
||
case AND:
|
||
case IOR:
|
||
case XOR:
|
||
return TRUE;
|
||
}
|
||
}
|
||
|
||
/* Return true if operator is an integer division operator that can executed
|
||
conditionally. */
|
||
|
||
int
|
||
condexec_si_divide_operator (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
enum machine_mode op_mode = GET_MODE (op);
|
||
|
||
if (mode != VOIDmode && op_mode != mode)
|
||
return FALSE;
|
||
|
||
switch (GET_CODE (op))
|
||
{
|
||
default:
|
||
return FALSE;
|
||
|
||
case DIV:
|
||
case UDIV:
|
||
return TRUE;
|
||
}
|
||
}
|
||
|
||
/* Return true if operator is an integer unary operator that can executed
|
||
conditionally. */
|
||
|
||
int
|
||
condexec_si_unary_operator (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
enum machine_mode op_mode = GET_MODE (op);
|
||
|
||
if (mode != VOIDmode && op_mode != mode)
|
||
return FALSE;
|
||
|
||
switch (GET_CODE (op))
|
||
{
|
||
default:
|
||
return FALSE;
|
||
|
||
case NEG:
|
||
case NOT:
|
||
return TRUE;
|
||
}
|
||
}
|
||
|
||
/* Return true if operator is a conversion-type expression that can be
|
||
evaluated conditionally by floating-point instructions. */
|
||
|
||
int
|
||
condexec_sf_conv_operator (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
enum machine_mode op_mode = GET_MODE (op);
|
||
|
||
if (mode != VOIDmode && op_mode != mode)
|
||
return FALSE;
|
||
|
||
switch (GET_CODE (op))
|
||
{
|
||
default:
|
||
return FALSE;
|
||
|
||
case NEG:
|
||
case ABS:
|
||
return TRUE;
|
||
}
|
||
}
|
||
|
||
/* Return true if operator is an addition or subtraction expression.
|
||
Such expressions can be evaluated conditionally by floating-point
|
||
instructions. */
|
||
|
||
int
|
||
condexec_sf_add_operator (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
enum machine_mode op_mode = GET_MODE (op);
|
||
|
||
if (mode != VOIDmode && op_mode != mode)
|
||
return FALSE;
|
||
|
||
switch (GET_CODE (op))
|
||
{
|
||
default:
|
||
return FALSE;
|
||
|
||
case PLUS:
|
||
case MINUS:
|
||
return TRUE;
|
||
}
|
||
}
|
||
|
||
/* Return true if the memory operand is one that can be conditionally
|
||
executed. */
|
||
|
||
int
|
||
condexec_memory_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
enum machine_mode op_mode = GET_MODE (op);
|
||
rtx addr;
|
||
|
||
if (mode != VOIDmode && op_mode != mode)
|
||
return FALSE;
|
||
|
||
switch (op_mode)
|
||
{
|
||
default:
|
||
return FALSE;
|
||
|
||
case QImode:
|
||
case HImode:
|
||
case SImode:
|
||
case SFmode:
|
||
break;
|
||
}
|
||
|
||
if (GET_CODE (op) != MEM)
|
||
return FALSE;
|
||
|
||
addr = XEXP (op, 0);
|
||
if (GET_CODE (addr) == ADDRESSOF)
|
||
return TRUE;
|
||
|
||
return frv_legitimate_address_p (mode, addr, reload_completed, TRUE);
|
||
}
|
||
|
||
/* Return true if operator is an integer binary operator that can be combined
|
||
with a setcc operation. Do not allow the arithmetic operations that could
|
||
potentially overflow since the FR-V sets the condition code based on the
|
||
"true" value of the result, not the result after truncating to a 32-bit
|
||
register. */
|
||
|
||
int
|
||
intop_compare_operator (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
enum machine_mode op_mode = GET_MODE (op);
|
||
|
||
if (mode != VOIDmode && op_mode != mode)
|
||
return FALSE;
|
||
|
||
switch (GET_CODE (op))
|
||
{
|
||
default:
|
||
return FALSE;
|
||
|
||
case AND:
|
||
case IOR:
|
||
case XOR:
|
||
case ASHIFTRT:
|
||
case LSHIFTRT:
|
||
break;
|
||
}
|
||
|
||
if (! integer_register_operand (XEXP (op, 0), SImode))
|
||
return FALSE;
|
||
|
||
if (! gpr_or_int10_operand (XEXP (op, 1), SImode))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* Return true if operator is an integer binary operator that can be combined
|
||
with a setcc operation inside of a conditional execution. */
|
||
|
||
int
|
||
condexec_intop_cmp_operator (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
enum machine_mode op_mode = GET_MODE (op);
|
||
|
||
if (mode != VOIDmode && op_mode != mode)
|
||
return FALSE;
|
||
|
||
switch (GET_CODE (op))
|
||
{
|
||
default:
|
||
return FALSE;
|
||
|
||
case AND:
|
||
case IOR:
|
||
case XOR:
|
||
case ASHIFTRT:
|
||
case LSHIFTRT:
|
||
break;
|
||
}
|
||
|
||
if (! integer_register_operand (XEXP (op, 0), SImode))
|
||
return FALSE;
|
||
|
||
if (! integer_register_operand (XEXP (op, 1), SImode))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* Return 1 if operand is a valid ACC register number */
|
||
|
||
int
|
||
acc_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
int regno;
|
||
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) == SUBREG)
|
||
{
|
||
if (GET_CODE (SUBREG_REG (op)) != REG)
|
||
return register_operand (op, mode);
|
||
|
||
op = SUBREG_REG (op);
|
||
}
|
||
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
regno = REGNO (op);
|
||
return ACC_OR_PSEUDO_P (regno);
|
||
}
|
||
|
||
/* Return 1 if operand is a valid even ACC register number */
|
||
|
||
int
|
||
even_acc_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
int regno;
|
||
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) == SUBREG)
|
||
{
|
||
if (GET_CODE (SUBREG_REG (op)) != REG)
|
||
return register_operand (op, mode);
|
||
|
||
op = SUBREG_REG (op);
|
||
}
|
||
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
regno = REGNO (op);
|
||
return (ACC_OR_PSEUDO_P (regno) && ((regno - ACC_FIRST) & 1) == 0);
|
||
}
|
||
|
||
/* Return 1 if operand is zero or four */
|
||
|
||
int
|
||
quad_acc_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
int regno;
|
||
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) == SUBREG)
|
||
{
|
||
if (GET_CODE (SUBREG_REG (op)) != REG)
|
||
return register_operand (op, mode);
|
||
|
||
op = SUBREG_REG (op);
|
||
}
|
||
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
regno = REGNO (op);
|
||
return (ACC_OR_PSEUDO_P (regno) && ((regno - ACC_FIRST) & 3) == 0);
|
||
}
|
||
|
||
/* Return 1 if operand is a valid ACCG register number */
|
||
|
||
int
|
||
accg_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
if (GET_MODE (op) != mode && mode != VOIDmode)
|
||
return FALSE;
|
||
|
||
if (GET_CODE (op) == SUBREG)
|
||
{
|
||
if (GET_CODE (SUBREG_REG (op)) != REG)
|
||
return register_operand (op, mode);
|
||
|
||
op = SUBREG_REG (op);
|
||
}
|
||
|
||
if (GET_CODE (op) != REG)
|
||
return FALSE;
|
||
|
||
return ACCG_OR_PSEUDO_P (REGNO (op));
|
||
}
|
||
|
||
|
||
/* Return true if the bare return instruction can be used outside of the
|
||
epilog code. For frv, we only do it if there was no stack allocation. */
|
||
|
||
int
|
||
direct_return_p ()
|
||
{
|
||
frv_stack_t *info;
|
||
|
||
if (!reload_completed)
|
||
return FALSE;
|
||
|
||
info = frv_stack_info ();
|
||
return (info->total_size == 0);
|
||
}
|
||
|
||
|
||
/* Emit code to handle a MOVSI, adding in the small data register or pic
|
||
register if needed to load up addresses. Return TRUE if the appropriate
|
||
instructions are emitted. */
|
||
|
||
int
|
||
frv_emit_movsi (dest, src)
|
||
rtx dest;
|
||
rtx src;
|
||
{
|
||
int base_regno = -1;
|
||
|
||
if (!reload_in_progress
|
||
&& !reload_completed
|
||
&& !register_operand (dest, SImode)
|
||
&& (!reg_or_0_operand (src, SImode)
|
||
/* Virtual registers will almost always be replaced by an
|
||
add instruction, so expose this to CSE by copying to
|
||
an intermediate register */
|
||
|| (GET_CODE (src) == REG
|
||
&& IN_RANGE_P (REGNO (src),
|
||
FIRST_VIRTUAL_REGISTER,
|
||
LAST_VIRTUAL_REGISTER))))
|
||
{
|
||
emit_insn (gen_rtx_SET (VOIDmode, dest, copy_to_mode_reg (SImode, src)));
|
||
return TRUE;
|
||
}
|
||
|
||
/* Explicitly add in the PIC or small data register if needed. */
|
||
switch (GET_CODE (src))
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case LABEL_REF:
|
||
if (flag_pic)
|
||
base_regno = PIC_REGNO;
|
||
|
||
break;
|
||
|
||
case CONST:
|
||
if (const_small_data_p (src))
|
||
base_regno = SDA_BASE_REG;
|
||
|
||
else if (flag_pic)
|
||
base_regno = PIC_REGNO;
|
||
|
||
break;
|
||
|
||
case SYMBOL_REF:
|
||
if (symbol_ref_small_data_p (src))
|
||
base_regno = SDA_BASE_REG;
|
||
|
||
else if (flag_pic)
|
||
base_regno = PIC_REGNO;
|
||
|
||
break;
|
||
}
|
||
|
||
if (base_regno >= 0)
|
||
{
|
||
emit_insn (gen_rtx_SET (VOIDmode, dest,
|
||
gen_rtx_PLUS (Pmode,
|
||
gen_rtx_REG (Pmode, base_regno),
|
||
src)));
|
||
|
||
if (base_regno == PIC_REGNO)
|
||
cfun->uses_pic_offset_table = TRUE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
/* Return a string to output a single word move. */
|
||
|
||
const char *
|
||
output_move_single (operands, insn)
|
||
rtx operands[];
|
||
rtx insn;
|
||
{
|
||
rtx dest = operands[0];
|
||
rtx src = operands[1];
|
||
|
||
if (GET_CODE (dest) == REG)
|
||
{
|
||
int dest_regno = REGNO (dest);
|
||
enum machine_mode mode = GET_MODE (dest);
|
||
|
||
if (GPR_P (dest_regno))
|
||
{
|
||
if (GET_CODE (src) == REG)
|
||
{
|
||
/* gpr <- some sort of register */
|
||
int src_regno = REGNO (src);
|
||
|
||
if (GPR_P (src_regno))
|
||
return "mov %1, %0";
|
||
|
||
else if (FPR_P (src_regno))
|
||
return "movfg %1, %0";
|
||
|
||
else if (SPR_P (src_regno))
|
||
return "movsg %1, %0";
|
||
}
|
||
|
||
else if (GET_CODE (src) == MEM)
|
||
{
|
||
/* gpr <- memory */
|
||
switch (mode)
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case QImode:
|
||
return "ldsb%I1%U1 %M1,%0";
|
||
|
||
case HImode:
|
||
return "ldsh%I1%U1 %M1,%0";
|
||
|
||
case SImode:
|
||
case SFmode:
|
||
return "ld%I1%U1 %M1, %0";
|
||
}
|
||
}
|
||
|
||
else if (GET_CODE (src) == CONST_INT
|
||
|| GET_CODE (src) == CONST_DOUBLE)
|
||
{
|
||
/* gpr <- integer/floating constant */
|
||
HOST_WIDE_INT value;
|
||
|
||
if (GET_CODE (src) == CONST_INT)
|
||
value = INTVAL (src);
|
||
|
||
else if (mode == SFmode)
|
||
{
|
||
REAL_VALUE_TYPE rv;
|
||
long l;
|
||
|
||
REAL_VALUE_FROM_CONST_DOUBLE (rv, src);
|
||
REAL_VALUE_TO_TARGET_SINGLE (rv, l);
|
||
value = l;
|
||
}
|
||
|
||
else
|
||
value = CONST_DOUBLE_LOW (src);
|
||
|
||
if (IN_RANGE_P (value, -32768, 32767))
|
||
return "setlos %1, %0";
|
||
|
||
return "#";
|
||
}
|
||
|
||
else if (GET_CODE (src) == SYMBOL_REF
|
||
|| GET_CODE (src) == LABEL_REF
|
||
|| GET_CODE (src) == CONST)
|
||
{
|
||
/* Silently fix up instances where the small data pointer is not
|
||
used in the address. */
|
||
if (small_data_symbolic_operand (src, GET_MODE (src)))
|
||
return "addi %@, #gprel12(%1), %0";
|
||
|
||
return "#";
|
||
}
|
||
}
|
||
|
||
else if (FPR_P (dest_regno))
|
||
{
|
||
if (GET_CODE (src) == REG)
|
||
{
|
||
/* fpr <- some sort of register */
|
||
int src_regno = REGNO (src);
|
||
|
||
if (GPR_P (src_regno))
|
||
return "movgf %1, %0";
|
||
|
||
else if (FPR_P (src_regno))
|
||
{
|
||
if (TARGET_HARD_FLOAT)
|
||
return "fmovs %1, %0";
|
||
else
|
||
return "mor %1, %1, %0";
|
||
}
|
||
}
|
||
|
||
else if (GET_CODE (src) == MEM)
|
||
{
|
||
/* fpr <- memory */
|
||
switch (mode)
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case QImode:
|
||
return "ldbf%I1%U1 %M1,%0";
|
||
|
||
case HImode:
|
||
return "ldhf%I1%U1 %M1,%0";
|
||
|
||
case SImode:
|
||
case SFmode:
|
||
return "ldf%I1%U1 %M1, %0";
|
||
}
|
||
}
|
||
|
||
else if (ZERO_P (src))
|
||
return "movgf %., %0";
|
||
}
|
||
|
||
else if (SPR_P (dest_regno))
|
||
{
|
||
if (GET_CODE (src) == REG)
|
||
{
|
||
/* spr <- some sort of register */
|
||
int src_regno = REGNO (src);
|
||
|
||
if (GPR_P (src_regno))
|
||
return "movgs %1, %0";
|
||
}
|
||
}
|
||
}
|
||
|
||
else if (GET_CODE (dest) == MEM)
|
||
{
|
||
if (GET_CODE (src) == REG)
|
||
{
|
||
int src_regno = REGNO (src);
|
||
enum machine_mode mode = GET_MODE (dest);
|
||
|
||
if (GPR_P (src_regno))
|
||
{
|
||
switch (mode)
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case QImode:
|
||
return "stb%I0%U0 %1, %M0";
|
||
|
||
case HImode:
|
||
return "sth%I0%U0 %1, %M0";
|
||
|
||
case SImode:
|
||
case SFmode:
|
||
return "st%I0%U0 %1, %M0";
|
||
}
|
||
}
|
||
|
||
else if (FPR_P (src_regno))
|
||
{
|
||
switch (mode)
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case QImode:
|
||
return "stbf%I0%U0 %1, %M0";
|
||
|
||
case HImode:
|
||
return "sthf%I0%U0 %1, %M0";
|
||
|
||
case SImode:
|
||
case SFmode:
|
||
return "stf%I0%U0 %1, %M0";
|
||
}
|
||
}
|
||
}
|
||
|
||
else if (ZERO_P (src))
|
||
{
|
||
switch (GET_MODE (dest))
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case QImode:
|
||
return "stb%I0%U0 %., %M0";
|
||
|
||
case HImode:
|
||
return "sth%I0%U0 %., %M0";
|
||
|
||
case SImode:
|
||
case SFmode:
|
||
return "st%I0%U0 %., %M0";
|
||
}
|
||
}
|
||
}
|
||
|
||
fatal_insn ("Bad output_move_single operand", insn);
|
||
return "";
|
||
}
|
||
|
||
|
||
/* Return a string to output a double word move. */
|
||
|
||
const char *
|
||
output_move_double (operands, insn)
|
||
rtx operands[];
|
||
rtx insn;
|
||
{
|
||
rtx dest = operands[0];
|
||
rtx src = operands[1];
|
||
enum machine_mode mode = GET_MODE (dest);
|
||
|
||
if (GET_CODE (dest) == REG)
|
||
{
|
||
int dest_regno = REGNO (dest);
|
||
|
||
if (GPR_P (dest_regno))
|
||
{
|
||
if (GET_CODE (src) == REG)
|
||
{
|
||
/* gpr <- some sort of register */
|
||
int src_regno = REGNO (src);
|
||
|
||
if (GPR_P (src_regno))
|
||
return "#";
|
||
|
||
else if (FPR_P (src_regno))
|
||
{
|
||
if (((dest_regno - GPR_FIRST) & 1) == 0
|
||
&& ((src_regno - FPR_FIRST) & 1) == 0)
|
||
return "movfgd %1, %0";
|
||
|
||
return "#";
|
||
}
|
||
}
|
||
|
||
else if (GET_CODE (src) == MEM)
|
||
{
|
||
/* gpr <- memory */
|
||
if (dbl_memory_one_insn_operand (src, mode))
|
||
return "ldd%I1%U1 %M1, %0";
|
||
|
||
return "#";
|
||
}
|
||
|
||
else if (GET_CODE (src) == CONST_INT
|
||
|| GET_CODE (src) == CONST_DOUBLE)
|
||
return "#";
|
||
}
|
||
|
||
else if (FPR_P (dest_regno))
|
||
{
|
||
if (GET_CODE (src) == REG)
|
||
{
|
||
/* fpr <- some sort of register */
|
||
int src_regno = REGNO (src);
|
||
|
||
if (GPR_P (src_regno))
|
||
{
|
||
if (((dest_regno - FPR_FIRST) & 1) == 0
|
||
&& ((src_regno - GPR_FIRST) & 1) == 0)
|
||
return "movgfd %1, %0";
|
||
|
||
return "#";
|
||
}
|
||
|
||
else if (FPR_P (src_regno))
|
||
{
|
||
if (TARGET_DOUBLE
|
||
&& ((dest_regno - FPR_FIRST) & 1) == 0
|
||
&& ((src_regno - FPR_FIRST) & 1) == 0)
|
||
return "fmovd %1, %0";
|
||
|
||
return "#";
|
||
}
|
||
}
|
||
|
||
else if (GET_CODE (src) == MEM)
|
||
{
|
||
/* fpr <- memory */
|
||
if (dbl_memory_one_insn_operand (src, mode))
|
||
return "lddf%I1%U1 %M1, %0";
|
||
|
||
return "#";
|
||
}
|
||
|
||
else if (ZERO_P (src))
|
||
return "#";
|
||
}
|
||
}
|
||
|
||
else if (GET_CODE (dest) == MEM)
|
||
{
|
||
if (GET_CODE (src) == REG)
|
||
{
|
||
int src_regno = REGNO (src);
|
||
|
||
if (GPR_P (src_regno))
|
||
{
|
||
if (((src_regno - GPR_FIRST) & 1) == 0
|
||
&& dbl_memory_one_insn_operand (dest, mode))
|
||
return "std%I0%U0 %1, %M0";
|
||
|
||
return "#";
|
||
}
|
||
|
||
if (FPR_P (src_regno))
|
||
{
|
||
if (((src_regno - FPR_FIRST) & 1) == 0
|
||
&& dbl_memory_one_insn_operand (dest, mode))
|
||
return "stdf%I0%U0 %1, %M0";
|
||
|
||
return "#";
|
||
}
|
||
}
|
||
|
||
else if (ZERO_P (src))
|
||
{
|
||
if (dbl_memory_one_insn_operand (dest, mode))
|
||
return "std%I0%U0 %., %M0";
|
||
|
||
return "#";
|
||
}
|
||
}
|
||
|
||
fatal_insn ("Bad output_move_double operand", insn);
|
||
return "";
|
||
}
|
||
|
||
|
||
/* Return a string to output a single word conditional move.
|
||
Operand0 -- EQ/NE of ccr register and 0
|
||
Operand1 -- CCR register
|
||
Operand2 -- destination
|
||
Operand3 -- source */
|
||
|
||
const char *
|
||
output_condmove_single (operands, insn)
|
||
rtx operands[];
|
||
rtx insn;
|
||
{
|
||
rtx dest = operands[2];
|
||
rtx src = operands[3];
|
||
|
||
if (GET_CODE (dest) == REG)
|
||
{
|
||
int dest_regno = REGNO (dest);
|
||
enum machine_mode mode = GET_MODE (dest);
|
||
|
||
if (GPR_P (dest_regno))
|
||
{
|
||
if (GET_CODE (src) == REG)
|
||
{
|
||
/* gpr <- some sort of register */
|
||
int src_regno = REGNO (src);
|
||
|
||
if (GPR_P (src_regno))
|
||
return "cmov %z3, %2, %1, %e0";
|
||
|
||
else if (FPR_P (src_regno))
|
||
return "cmovfg %3, %2, %1, %e0";
|
||
}
|
||
|
||
else if (GET_CODE (src) == MEM)
|
||
{
|
||
/* gpr <- memory */
|
||
switch (mode)
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case QImode:
|
||
return "cldsb%I3%U3 %M3, %2, %1, %e0";
|
||
|
||
case HImode:
|
||
return "cldsh%I3%U3 %M3, %2, %1, %e0";
|
||
|
||
case SImode:
|
||
case SFmode:
|
||
return "cld%I3%U3 %M3, %2, %1, %e0";
|
||
}
|
||
}
|
||
|
||
else if (ZERO_P (src))
|
||
return "cmov %., %2, %1, %e0";
|
||
}
|
||
|
||
else if (FPR_P (dest_regno))
|
||
{
|
||
if (GET_CODE (src) == REG)
|
||
{
|
||
/* fpr <- some sort of register */
|
||
int src_regno = REGNO (src);
|
||
|
||
if (GPR_P (src_regno))
|
||
return "cmovgf %3, %2, %1, %e0";
|
||
|
||
else if (FPR_P (src_regno))
|
||
{
|
||
if (TARGET_HARD_FLOAT)
|
||
return "cfmovs %3,%2,%1,%e0";
|
||
else
|
||
return "cmor %3, %3, %2, %1, %e0";
|
||
}
|
||
}
|
||
|
||
else if (GET_CODE (src) == MEM)
|
||
{
|
||
/* fpr <- memory */
|
||
if (mode == SImode || mode == SFmode)
|
||
return "cldf%I3%U3 %M3, %2, %1, %e0";
|
||
}
|
||
|
||
else if (ZERO_P (src))
|
||
return "cmovgf %., %2, %1, %e0";
|
||
}
|
||
}
|
||
|
||
else if (GET_CODE (dest) == MEM)
|
||
{
|
||
if (GET_CODE (src) == REG)
|
||
{
|
||
int src_regno = REGNO (src);
|
||
enum machine_mode mode = GET_MODE (dest);
|
||
|
||
if (GPR_P (src_regno))
|
||
{
|
||
switch (mode)
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case QImode:
|
||
return "cstb%I2%U2 %3, %M2, %1, %e0";
|
||
|
||
case HImode:
|
||
return "csth%I2%U2 %3, %M2, %1, %e0";
|
||
|
||
case SImode:
|
||
case SFmode:
|
||
return "cst%I2%U2 %3, %M2, %1, %e0";
|
||
}
|
||
}
|
||
|
||
else if (FPR_P (src_regno) && (mode == SImode || mode == SFmode))
|
||
return "cstf%I2%U2 %3, %M2, %1, %e0";
|
||
}
|
||
|
||
else if (ZERO_P (src))
|
||
{
|
||
enum machine_mode mode = GET_MODE (dest);
|
||
switch (mode)
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case QImode:
|
||
return "cstb%I2%U2 %., %M2, %1, %e0";
|
||
|
||
case HImode:
|
||
return "csth%I2%U2 %., %M2, %1, %e0";
|
||
|
||
case SImode:
|
||
case SFmode:
|
||
return "cst%I2%U2 %., %M2, %1, %e0";
|
||
}
|
||
}
|
||
}
|
||
|
||
fatal_insn ("Bad output_condmove_single operand", insn);
|
||
return "";
|
||
}
|
||
|
||
|
||
/* Emit the appropriate code to do a comparison, returning the register the
|
||
comparison was done it. */
|
||
|
||
static rtx
|
||
frv_emit_comparison (test, op0, op1)
|
||
enum rtx_code test;
|
||
rtx op0;
|
||
rtx op1;
|
||
{
|
||
enum machine_mode cc_mode;
|
||
rtx cc_reg;
|
||
|
||
/* Floating point doesn't have comparison against a constant */
|
||
if (GET_MODE (op0) == CC_FPmode && GET_CODE (op1) != REG)
|
||
op1 = force_reg (GET_MODE (op0), op1);
|
||
|
||
/* Possibly disable using anything but a fixed register in order to work
|
||
around cse moving comparisons past function calls. */
|
||
cc_mode = SELECT_CC_MODE (test, op0, op1);
|
||
cc_reg = ((TARGET_ALLOC_CC)
|
||
? gen_reg_rtx (cc_mode)
|
||
: gen_rtx_REG (cc_mode,
|
||
(cc_mode == CC_FPmode) ? FCC_FIRST : ICC_FIRST));
|
||
|
||
emit_insn (gen_rtx_SET (VOIDmode, cc_reg,
|
||
gen_rtx_COMPARE (cc_mode, op0, op1)));
|
||
|
||
return cc_reg;
|
||
}
|
||
|
||
|
||
/* Emit code for a conditional branch. The comparison operands were previously
|
||
stored in frv_compare_op0 and frv_compare_op1.
|
||
|
||
XXX: I originally wanted to add a clobber of a CCR register to use in
|
||
conditional execution, but that confuses the rest of the compiler. */
|
||
|
||
int
|
||
frv_emit_cond_branch (test, label)
|
||
enum rtx_code test;
|
||
rtx label;
|
||
{
|
||
rtx test_rtx;
|
||
rtx label_ref;
|
||
rtx if_else;
|
||
rtx cc_reg = frv_emit_comparison (test, frv_compare_op0, frv_compare_op1);
|
||
enum machine_mode cc_mode = GET_MODE (cc_reg);
|
||
|
||
/* Branches generate:
|
||
(set (pc)
|
||
(if_then_else (<test>, <cc_reg>, (const_int 0))
|
||
(label_ref <branch_label>)
|
||
(pc))) */
|
||
label_ref = gen_rtx_LABEL_REF (VOIDmode, label);
|
||
test_rtx = gen_rtx (test, cc_mode, cc_reg, const0_rtx);
|
||
if_else = gen_rtx_IF_THEN_ELSE (cc_mode, test_rtx, label_ref, pc_rtx);
|
||
emit_jump_insn (gen_rtx_SET (VOIDmode, pc_rtx, if_else));
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
/* Emit code to set a gpr to 1/0 based on a comparison. The comparison
|
||
operands were previously stored in frv_compare_op0 and frv_compare_op1. */
|
||
|
||
int
|
||
frv_emit_scc (test, target)
|
||
enum rtx_code test;
|
||
rtx target;
|
||
{
|
||
rtx set;
|
||
rtx test_rtx;
|
||
rtx clobber;
|
||
rtx cr_reg;
|
||
rtx cc_reg = frv_emit_comparison (test, frv_compare_op0, frv_compare_op1);
|
||
|
||
/* SCC instructions generate:
|
||
(parallel [(set <target> (<test>, <cc_reg>, (const_int 0))
|
||
(clobber (<ccr_reg>))]) */
|
||
test_rtx = gen_rtx_fmt_ee (test, SImode, cc_reg, const0_rtx);
|
||
set = gen_rtx_SET (VOIDmode, target, test_rtx);
|
||
|
||
cr_reg = ((TARGET_ALLOC_CC)
|
||
? gen_reg_rtx (CC_CCRmode)
|
||
: gen_rtx_REG (CC_CCRmode,
|
||
((GET_MODE (cc_reg) == CC_FPmode)
|
||
? FCR_FIRST
|
||
: ICR_FIRST)));
|
||
|
||
clobber = gen_rtx_CLOBBER (VOIDmode, cr_reg);
|
||
emit_insn (gen_rtx_PARALLEL (VOIDmode, gen_rtvec (2, set, clobber)));
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
/* Split a SCC instruction into component parts, returning a SEQUENCE to hold
|
||
the seperate insns. */
|
||
|
||
rtx
|
||
frv_split_scc (dest, test, cc_reg, cr_reg, value)
|
||
rtx dest;
|
||
rtx test;
|
||
rtx cc_reg;
|
||
rtx cr_reg;
|
||
HOST_WIDE_INT value;
|
||
{
|
||
rtx ret;
|
||
|
||
start_sequence ();
|
||
|
||
/* Set the appropriate CCR bit. */
|
||
emit_insn (gen_rtx_SET (VOIDmode,
|
||
cr_reg,
|
||
gen_rtx_fmt_ee (GET_CODE (test),
|
||
GET_MODE (cr_reg),
|
||
cc_reg,
|
||
const0_rtx)));
|
||
|
||
/* Move the value into the destination. */
|
||
emit_move_insn (dest, GEN_INT (value));
|
||
|
||
/* Move 0 into the destination if the test failed */
|
||
emit_insn (gen_rtx_COND_EXEC (VOIDmode,
|
||
gen_rtx_EQ (GET_MODE (cr_reg),
|
||
cr_reg,
|
||
const0_rtx),
|
||
gen_rtx_SET (VOIDmode, dest, const0_rtx)));
|
||
|
||
/* Finish up, return sequence. */
|
||
ret = get_insns ();
|
||
end_sequence ();
|
||
return ret;
|
||
}
|
||
|
||
|
||
/* Emit the code for a conditional move, return TRUE if we could do the
|
||
move. */
|
||
|
||
int
|
||
frv_emit_cond_move (dest, test_rtx, src1, src2)
|
||
rtx dest;
|
||
rtx test_rtx;
|
||
rtx src1;
|
||
rtx src2;
|
||
{
|
||
rtx set;
|
||
rtx clobber_cc;
|
||
rtx test2;
|
||
rtx cr_reg;
|
||
rtx if_rtx;
|
||
enum rtx_code test = GET_CODE (test_rtx);
|
||
rtx cc_reg = frv_emit_comparison (test, frv_compare_op0, frv_compare_op1);
|
||
enum machine_mode cc_mode = GET_MODE (cc_reg);
|
||
|
||
/* Conditional move instructions generate:
|
||
(parallel [(set <target>
|
||
(if_then_else (<test> <cc_reg> (const_int 0))
|
||
<src1>
|
||
<src2>))
|
||
(clobber (<ccr_reg>))]) */
|
||
|
||
/* Handle various cases of conditional move involving two constants. */
|
||
if (GET_CODE (src1) == CONST_INT && GET_CODE (src2) == CONST_INT)
|
||
{
|
||
HOST_WIDE_INT value1 = INTVAL (src1);
|
||
HOST_WIDE_INT value2 = INTVAL (src2);
|
||
|
||
/* having 0 as one of the constants can be done by loading the other
|
||
constant, and optionally moving in gr0. */
|
||
if (value1 == 0 || value2 == 0)
|
||
;
|
||
|
||
/* If the first value is within an addi range and also the difference
|
||
between the two fits in an addi's range, load up the difference, then
|
||
conditionally move in 0, and then unconditionally add the first
|
||
value. */
|
||
else if (IN_RANGE_P (value1, -2048, 2047)
|
||
&& IN_RANGE_P (value2 - value1, -2048, 2047))
|
||
;
|
||
|
||
/* If neither condition holds, just force the constant into a
|
||
register. */
|
||
else
|
||
{
|
||
src1 = force_reg (GET_MODE (dest), src1);
|
||
src2 = force_reg (GET_MODE (dest), src2);
|
||
}
|
||
}
|
||
|
||
/* If one value is a register, insure the other value is either 0 or a
|
||
register. */
|
||
else
|
||
{
|
||
if (GET_CODE (src1) == CONST_INT && INTVAL (src1) != 0)
|
||
src1 = force_reg (GET_MODE (dest), src1);
|
||
|
||
if (GET_CODE (src2) == CONST_INT && INTVAL (src2) != 0)
|
||
src2 = force_reg (GET_MODE (dest), src2);
|
||
}
|
||
|
||
test2 = gen_rtx_fmt_ee (test, cc_mode, cc_reg, const0_rtx);
|
||
if_rtx = gen_rtx_IF_THEN_ELSE (GET_MODE (dest), test2, src1, src2);
|
||
|
||
set = gen_rtx_SET (VOIDmode, dest, if_rtx);
|
||
|
||
cr_reg = ((TARGET_ALLOC_CC)
|
||
? gen_reg_rtx (CC_CCRmode)
|
||
: gen_rtx_REG (CC_CCRmode,
|
||
(cc_mode == CC_FPmode) ? FCR_FIRST : ICR_FIRST));
|
||
|
||
clobber_cc = gen_rtx_CLOBBER (VOIDmode, cr_reg);
|
||
emit_insn (gen_rtx_PARALLEL (VOIDmode, gen_rtvec (2, set, clobber_cc)));
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
/* Split a conditonal move into constituent parts, returning a SEQUENCE
|
||
containing all of the insns. */
|
||
|
||
rtx
|
||
frv_split_cond_move (operands)
|
||
rtx operands[];
|
||
{
|
||
rtx dest = operands[0];
|
||
rtx test = operands[1];
|
||
rtx cc_reg = operands[2];
|
||
rtx src1 = operands[3];
|
||
rtx src2 = operands[4];
|
||
rtx cr_reg = operands[5];
|
||
rtx ret;
|
||
enum machine_mode cr_mode = GET_MODE (cr_reg);
|
||
|
||
start_sequence ();
|
||
|
||
/* Set the appropriate CCR bit. */
|
||
emit_insn (gen_rtx_SET (VOIDmode,
|
||
cr_reg,
|
||
gen_rtx_fmt_ee (GET_CODE (test),
|
||
GET_MODE (cr_reg),
|
||
cc_reg,
|
||
const0_rtx)));
|
||
|
||
/* Handle various cases of conditional move involving two constants. */
|
||
if (GET_CODE (src1) == CONST_INT && GET_CODE (src2) == CONST_INT)
|
||
{
|
||
HOST_WIDE_INT value1 = INTVAL (src1);
|
||
HOST_WIDE_INT value2 = INTVAL (src2);
|
||
|
||
/* having 0 as one of the constants can be done by loading the other
|
||
constant, and optionally moving in gr0. */
|
||
if (value1 == 0)
|
||
{
|
||
emit_move_insn (dest, src2);
|
||
emit_insn (gen_rtx_COND_EXEC (VOIDmode,
|
||
gen_rtx_NE (cr_mode, cr_reg,
|
||
const0_rtx),
|
||
gen_rtx_SET (VOIDmode, dest, src1)));
|
||
}
|
||
|
||
else if (value2 == 0)
|
||
{
|
||
emit_move_insn (dest, src1);
|
||
emit_insn (gen_rtx_COND_EXEC (VOIDmode,
|
||
gen_rtx_EQ (cr_mode, cr_reg,
|
||
const0_rtx),
|
||
gen_rtx_SET (VOIDmode, dest, src2)));
|
||
}
|
||
|
||
/* If the first value is within an addi range and also the difference
|
||
between the two fits in an addi's range, load up the difference, then
|
||
conditionally move in 0, and then unconditionally add the first
|
||
value. */
|
||
else if (IN_RANGE_P (value1, -2048, 2047)
|
||
&& IN_RANGE_P (value2 - value1, -2048, 2047))
|
||
{
|
||
rtx dest_si = ((GET_MODE (dest) == SImode)
|
||
? dest
|
||
: gen_rtx_SUBREG (SImode, dest, 0));
|
||
|
||
emit_move_insn (dest_si, GEN_INT (value2 - value1));
|
||
emit_insn (gen_rtx_COND_EXEC (VOIDmode,
|
||
gen_rtx_NE (cr_mode, cr_reg,
|
||
const0_rtx),
|
||
gen_rtx_SET (VOIDmode, dest_si,
|
||
const0_rtx)));
|
||
emit_insn (gen_addsi3 (dest_si, dest_si, src1));
|
||
}
|
||
|
||
else
|
||
abort ();
|
||
}
|
||
else
|
||
{
|
||
/* Emit the conditional move for the test being true if needed. */
|
||
if (! rtx_equal_p (dest, src1))
|
||
emit_insn (gen_rtx_COND_EXEC (VOIDmode,
|
||
gen_rtx_NE (cr_mode, cr_reg, const0_rtx),
|
||
gen_rtx_SET (VOIDmode, dest, src1)));
|
||
|
||
/* Emit the conditional move for the test being false if needed. */
|
||
if (! rtx_equal_p (dest, src2))
|
||
emit_insn (gen_rtx_COND_EXEC (VOIDmode,
|
||
gen_rtx_EQ (cr_mode, cr_reg, const0_rtx),
|
||
gen_rtx_SET (VOIDmode, dest, src2)));
|
||
}
|
||
|
||
/* Finish up, return sequence. */
|
||
ret = get_insns ();
|
||
end_sequence ();
|
||
return ret;
|
||
}
|
||
|
||
|
||
/* Split (set DEST SOURCE), where DEST is a double register and SOURCE is a
|
||
memory location that is not known to be dword-aligned. */
|
||
void
|
||
frv_split_double_load (dest, source)
|
||
rtx dest;
|
||
rtx source;
|
||
{
|
||
int regno = REGNO (dest);
|
||
rtx dest1 = gen_highpart (SImode, dest);
|
||
rtx dest2 = gen_lowpart (SImode, dest);
|
||
rtx address = XEXP (source, 0);
|
||
|
||
/* If the address is pre-modified, load the lower-numbered register
|
||
first, then load the other register using an integer offset from
|
||
the modified base register. This order should always be safe,
|
||
since the pre-modification cannot affect the same registers as the
|
||
load does.
|
||
|
||
The situation for other loads is more complicated. Loading one
|
||
of the registers could affect the value of ADDRESS, so we must
|
||
be careful which order we do them in. */
|
||
if (GET_CODE (address) == PRE_MODIFY
|
||
|| ! refers_to_regno_p (regno, regno + 1, address, NULL))
|
||
{
|
||
/* It is safe to load the lower-numbered register first. */
|
||
emit_move_insn (dest1, change_address (source, SImode, NULL));
|
||
emit_move_insn (dest2, frv_index_memory (source, SImode, 1));
|
||
}
|
||
else
|
||
{
|
||
/* ADDRESS is not pre-modified and the address depends on the
|
||
lower-numbered register. Load the higher-numbered register
|
||
first. */
|
||
emit_move_insn (dest2, frv_index_memory (source, SImode, 1));
|
||
emit_move_insn (dest1, change_address (source, SImode, NULL));
|
||
}
|
||
}
|
||
|
||
/* Split (set DEST SOURCE), where DEST refers to a dword memory location
|
||
and SOURCE is either a double register or the constant zero. */
|
||
void
|
||
frv_split_double_store (dest, source)
|
||
rtx dest;
|
||
rtx source;
|
||
{
|
||
rtx dest1 = change_address (dest, SImode, NULL);
|
||
rtx dest2 = frv_index_memory (dest, SImode, 1);
|
||
if (ZERO_P (source))
|
||
{
|
||
emit_move_insn (dest1, CONST0_RTX (SImode));
|
||
emit_move_insn (dest2, CONST0_RTX (SImode));
|
||
}
|
||
else
|
||
{
|
||
emit_move_insn (dest1, gen_highpart (SImode, source));
|
||
emit_move_insn (dest2, gen_lowpart (SImode, source));
|
||
}
|
||
}
|
||
|
||
|
||
/* Split a min/max operation returning a SEQUENCE containing all of the
|
||
insns. */
|
||
|
||
rtx
|
||
frv_split_minmax (operands)
|
||
rtx operands[];
|
||
{
|
||
rtx dest = operands[0];
|
||
rtx minmax = operands[1];
|
||
rtx src1 = operands[2];
|
||
rtx src2 = operands[3];
|
||
rtx cc_reg = operands[4];
|
||
rtx cr_reg = operands[5];
|
||
rtx ret;
|
||
enum rtx_code test_code;
|
||
enum machine_mode cr_mode = GET_MODE (cr_reg);
|
||
|
||
start_sequence ();
|
||
|
||
/* Figure out which test to use */
|
||
switch (GET_CODE (minmax))
|
||
{
|
||
default:
|
||
abort ();
|
||
|
||
case SMIN: test_code = LT; break;
|
||
case SMAX: test_code = GT; break;
|
||
case UMIN: test_code = LTU; break;
|
||
case UMAX: test_code = GTU; break;
|
||
}
|
||
|
||
/* Issue the compare instruction. */
|
||
emit_insn (gen_rtx_SET (VOIDmode,
|
||
cc_reg,
|
||
gen_rtx_COMPARE (GET_MODE (cc_reg),
|
||
src1, src2)));
|
||
|
||
/* Set the appropriate CCR bit. */
|
||
emit_insn (gen_rtx_SET (VOIDmode,
|
||
cr_reg,
|
||
gen_rtx_fmt_ee (test_code,
|
||
GET_MODE (cr_reg),
|
||
cc_reg,
|
||
const0_rtx)));
|
||
|
||
/* If are taking the min/max of a nonzero constant, load that first, and
|
||
then do a conditional move of the other value. */
|
||
if (GET_CODE (src2) == CONST_INT && INTVAL (src2) != 0)
|
||
{
|
||
if (rtx_equal_p (dest, src1))
|
||
abort ();
|
||
|
||
emit_move_insn (dest, src2);
|
||
emit_insn (gen_rtx_COND_EXEC (VOIDmode,
|
||
gen_rtx_NE (cr_mode, cr_reg, const0_rtx),
|
||
gen_rtx_SET (VOIDmode, dest, src1)));
|
||
}
|
||
|
||
/* Otherwise, do each half of the move. */
|
||
else
|
||
{
|
||
/* Emit the conditional move for the test being true if needed. */
|
||
if (! rtx_equal_p (dest, src1))
|
||
emit_insn (gen_rtx_COND_EXEC (VOIDmode,
|
||
gen_rtx_NE (cr_mode, cr_reg, const0_rtx),
|
||
gen_rtx_SET (VOIDmode, dest, src1)));
|
||
|
||
/* Emit the conditional move for the test being false if needed. */
|
||
if (! rtx_equal_p (dest, src2))
|
||
emit_insn (gen_rtx_COND_EXEC (VOIDmode,
|
||
gen_rtx_EQ (cr_mode, cr_reg, const0_rtx),
|
||
gen_rtx_SET (VOIDmode, dest, src2)));
|
||
}
|
||
|
||
/* Finish up, return sequence. */
|
||
ret = get_insns ();
|
||
end_sequence ();
|
||
return ret;
|
||
}
|
||
|
||
|
||
/* Split an integer abs operation returning a SEQUENCE containing all of the
|
||
insns. */
|
||
|
||
rtx
|
||
frv_split_abs (operands)
|
||
rtx operands[];
|
||
{
|
||
rtx dest = operands[0];
|
||
rtx src = operands[1];
|
||
rtx cc_reg = operands[2];
|
||
rtx cr_reg = operands[3];
|
||
rtx ret;
|
||
|
||
start_sequence ();
|
||
|
||
/* Issue the compare < 0 instruction. */
|
||
emit_insn (gen_rtx_SET (VOIDmode,
|
||
cc_reg,
|
||
gen_rtx_COMPARE (CCmode, src, const0_rtx)));
|
||
|
||
/* Set the appropriate CCR bit. */
|
||
emit_insn (gen_rtx_SET (VOIDmode,
|
||
cr_reg,
|
||
gen_rtx_fmt_ee (LT, CC_CCRmode, cc_reg, const0_rtx)));
|
||
|
||
/* Emit the conditional negate if the value is negative */
|
||
emit_insn (gen_rtx_COND_EXEC (VOIDmode,
|
||
gen_rtx_NE (CC_CCRmode, cr_reg, const0_rtx),
|
||
gen_negsi2 (dest, src)));
|
||
|
||
/* Emit the conditional move for the test being false if needed. */
|
||
if (! rtx_equal_p (dest, src))
|
||
emit_insn (gen_rtx_COND_EXEC (VOIDmode,
|
||
gen_rtx_EQ (CC_CCRmode, cr_reg, const0_rtx),
|
||
gen_rtx_SET (VOIDmode, dest, src)));
|
||
|
||
/* Finish up, return sequence. */
|
||
ret = get_insns ();
|
||
end_sequence ();
|
||
return ret;
|
||
}
|
||
|
||
|
||
/* An internal function called by for_each_rtx to clear in a hard_reg set each
|
||
register used in an insn. */
|
||
|
||
static int
|
||
frv_clear_registers_used (ptr, data)
|
||
rtx *ptr;
|
||
void *data;
|
||
{
|
||
if (GET_CODE (*ptr) == REG)
|
||
{
|
||
int regno = REGNO (*ptr);
|
||
HARD_REG_SET *p_regs = (HARD_REG_SET *)data;
|
||
|
||
if (regno < FIRST_PSEUDO_REGISTER)
|
||
{
|
||
int reg_max = regno + HARD_REGNO_NREGS (regno, GET_MODE (*ptr));
|
||
|
||
while (regno < reg_max)
|
||
{
|
||
CLEAR_HARD_REG_BIT (*p_regs, regno);
|
||
regno++;
|
||
}
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
/* Initialize the extra fields provided by IFCVT_EXTRA_FIELDS. */
|
||
|
||
/* On the FR-V, we don't have any extra fields per se, but it is useful hook to
|
||
initialize the static storage. */
|
||
void
|
||
frv_ifcvt_init_extra_fields (ce_info)
|
||
ce_if_block_t *ce_info ATTRIBUTE_UNUSED;
|
||
{
|
||
frv_ifcvt.added_insns_list = NULL_RTX;
|
||
frv_ifcvt.cur_scratch_regs = 0;
|
||
frv_ifcvt.num_nested_cond_exec = 0;
|
||
frv_ifcvt.cr_reg = NULL_RTX;
|
||
frv_ifcvt.nested_cc_reg = NULL_RTX;
|
||
frv_ifcvt.extra_int_cr = NULL_RTX;
|
||
frv_ifcvt.extra_fp_cr = NULL_RTX;
|
||
frv_ifcvt.last_nested_if_cr = NULL_RTX;
|
||
}
|
||
|
||
|
||
/* Internal function to add a potenial insn to the list of insns to be inserted
|
||
if the conditional execution conversion is successful. */
|
||
|
||
static void
|
||
frv_ifcvt_add_insn (pattern, insn, before_p)
|
||
rtx pattern;
|
||
rtx insn;
|
||
int before_p;
|
||
{
|
||
rtx link = alloc_EXPR_LIST (VOIDmode, pattern, insn);
|
||
|
||
link->jump = before_p; /* mark to add this before or after insn */
|
||
frv_ifcvt.added_insns_list = alloc_EXPR_LIST (VOIDmode, link,
|
||
frv_ifcvt.added_insns_list);
|
||
|
||
if (TARGET_DEBUG_COND_EXEC)
|
||
{
|
||
fprintf (stderr,
|
||
"\n:::::::::: frv_ifcvt_add_insn: add the following %s insn %d:\n",
|
||
(before_p) ? "before" : "after",
|
||
(int)INSN_UID (insn));
|
||
|
||
debug_rtx (pattern);
|
||
}
|
||
}
|
||
|
||
|
||
/* A C expression to modify the code described by the conditional if
|
||
information CE_INFO, possibly updating the tests in TRUE_EXPR, and
|
||
FALSE_EXPR for converting if-then and if-then-else code to conditional
|
||
instructions. Set either TRUE_EXPR or FALSE_EXPR to a null pointer if the
|
||
tests cannot be converted. */
|
||
|
||
void
|
||
frv_ifcvt_modify_tests (ce_info, p_true, p_false)
|
||
ce_if_block_t *ce_info;
|
||
rtx *p_true;
|
||
rtx *p_false;
|
||
{
|
||
basic_block test_bb = ce_info->test_bb; /* test basic block */
|
||
basic_block then_bb = ce_info->then_bb; /* THEN */
|
||
basic_block else_bb = ce_info->else_bb; /* ELSE or NULL */
|
||
basic_block join_bb = ce_info->join_bb; /* join block or NULL */
|
||
rtx true_expr = *p_true;
|
||
rtx cr;
|
||
rtx cc;
|
||
rtx nested_cc;
|
||
enum machine_mode mode = GET_MODE (true_expr);
|
||
int j;
|
||
basic_block *bb;
|
||
int num_bb;
|
||
frv_tmp_reg_t *tmp_reg = &frv_ifcvt.tmp_reg;
|
||
rtx check_insn;
|
||
rtx sub_cond_exec_reg;
|
||
enum rtx_code code;
|
||
enum rtx_code code_true;
|
||
enum rtx_code code_false;
|
||
enum reg_class cc_class;
|
||
enum reg_class cr_class;
|
||
int cc_first;
|
||
int cc_last;
|
||
|
||
/* Make sure we are only dealing with hard registers. Also honor the
|
||
-mno-cond-exec switch, and -mno-nested-cond-exec switches if
|
||
applicable. */
|
||
if (!reload_completed || TARGET_NO_COND_EXEC
|
||
|| (TARGET_NO_NESTED_CE && ce_info->pass > 1))
|
||
goto fail;
|
||
|
||
/* Figure out which registers we can allocate for our own purposes. Only
|
||
consider registers that are not preserved across function calls and are
|
||
not fixed. However, allow the ICC/ICR temporary registers to be allocated
|
||
if we did not need to use them in reloading other registers. */
|
||
memset ((PTR) &tmp_reg->regs, 0, sizeof (tmp_reg->regs));
|
||
COPY_HARD_REG_SET (tmp_reg->regs, call_used_reg_set);
|
||
AND_COMPL_HARD_REG_SET (tmp_reg->regs, fixed_reg_set);
|
||
SET_HARD_REG_BIT (tmp_reg->regs, ICC_TEMP);
|
||
SET_HARD_REG_BIT (tmp_reg->regs, ICR_TEMP);
|
||
|
||
/* If this is a nested IF, we need to discover whether the CC registers that
|
||
are set/used inside of the block are used anywhere else. If not, we can
|
||
change them to be the CC register that is paired with the CR register that
|
||
controls the outermost IF block. */
|
||
if (ce_info->pass > 1)
|
||
{
|
||
CLEAR_HARD_REG_SET (frv_ifcvt.nested_cc_ok_rewrite);
|
||
for (j = CC_FIRST; j <= CC_LAST; j++)
|
||
if (TEST_HARD_REG_BIT (tmp_reg->regs, j))
|
||
{
|
||
if (REGNO_REG_SET_P (then_bb->global_live_at_start, j))
|
||
continue;
|
||
|
||
if (else_bb && REGNO_REG_SET_P (else_bb->global_live_at_start, j))
|
||
continue;
|
||
|
||
if (join_bb && REGNO_REG_SET_P (join_bb->global_live_at_start, j))
|
||
continue;
|
||
|
||
SET_HARD_REG_BIT (frv_ifcvt.nested_cc_ok_rewrite, j);
|
||
}
|
||
}
|
||
|
||
for (j = 0; j < frv_ifcvt.cur_scratch_regs; j++)
|
||
frv_ifcvt.scratch_regs[j] = NULL_RTX;
|
||
|
||
frv_ifcvt.added_insns_list = NULL_RTX;
|
||
frv_ifcvt.cur_scratch_regs = 0;
|
||
|
||
bb = (basic_block *) alloca ((2 + ce_info->num_multiple_test_blocks)
|
||
* sizeof (basic_block));
|
||
|
||
if (join_bb)
|
||
{
|
||
int regno;
|
||
|
||
/* Remove anything live at the beginning of the join block from being
|
||
available for allocation. */
|
||
EXECUTE_IF_SET_IN_REG_SET (join_bb->global_live_at_start, 0, regno,
|
||
{
|
||
if (regno < FIRST_PSEUDO_REGISTER)
|
||
CLEAR_HARD_REG_BIT (tmp_reg->regs, regno);
|
||
});
|
||
}
|
||
|
||
/* Add in all of the blocks in multiple &&/|| blocks to be scanned. */
|
||
num_bb = 0;
|
||
if (ce_info->num_multiple_test_blocks)
|
||
{
|
||
basic_block multiple_test_bb = ce_info->last_test_bb;
|
||
|
||
while (multiple_test_bb != test_bb)
|
||
{
|
||
bb[num_bb++] = multiple_test_bb;
|
||
multiple_test_bb = multiple_test_bb->pred->src;
|
||
}
|
||
}
|
||
|
||
/* Add in the THEN and ELSE blocks to be scanned. */
|
||
bb[num_bb++] = then_bb;
|
||
if (else_bb)
|
||
bb[num_bb++] = else_bb;
|
||
|
||
sub_cond_exec_reg = NULL_RTX;
|
||
frv_ifcvt.num_nested_cond_exec = 0;
|
||
|
||
/* Scan all of the blocks for registers that must not be allocated. */
|
||
for (j = 0; j < num_bb; j++)
|
||
{
|
||
rtx last_insn = bb[j]->end;
|
||
rtx insn = bb[j]->head;
|
||
int regno;
|
||
|
||
if (rtl_dump_file)
|
||
fprintf (rtl_dump_file, "Scanning %s block %d, start %d, end %d\n",
|
||
(bb[j] == else_bb) ? "else" : ((bb[j] == then_bb) ? "then" : "test"),
|
||
(int) bb[j]->index,
|
||
(int) INSN_UID (bb[j]->head),
|
||
(int) INSN_UID (bb[j]->end));
|
||
|
||
/* Anything live at the beginning of the block is obviously unavailable
|
||
for allocation. */
|
||
EXECUTE_IF_SET_IN_REG_SET (bb[j]->global_live_at_start, 0, regno,
|
||
{
|
||
if (regno < FIRST_PSEUDO_REGISTER)
|
||
CLEAR_HARD_REG_BIT (tmp_reg->regs, regno);
|
||
});
|
||
|
||
/* loop through the insns in the block. */
|
||
for (;;)
|
||
{
|
||
/* Mark any new registers that are created as being unavailable for
|
||
allocation. Also see if the CC register used in nested IFs can be
|
||
reallocated. */
|
||
if (INSN_P (insn))
|
||
{
|
||
rtx pattern;
|
||
rtx set;
|
||
int skip_nested_if = FALSE;
|
||
|
||
for_each_rtx (&PATTERN (insn), frv_clear_registers_used,
|
||
(void *)&tmp_reg->regs);
|
||
|
||
pattern = PATTERN (insn);
|
||
if (GET_CODE (pattern) == COND_EXEC)
|
||
{
|
||
rtx reg = XEXP (COND_EXEC_TEST (pattern), 0);
|
||
|
||
if (reg != sub_cond_exec_reg)
|
||
{
|
||
sub_cond_exec_reg = reg;
|
||
frv_ifcvt.num_nested_cond_exec++;
|
||
}
|
||
}
|
||
|
||
set = single_set_pattern (pattern);
|
||
if (set)
|
||
{
|
||
rtx dest = SET_DEST (set);
|
||
rtx src = SET_SRC (set);
|
||
|
||
if (GET_CODE (dest) == REG)
|
||
{
|
||
int regno = REGNO (dest);
|
||
enum rtx_code src_code = GET_CODE (src);
|
||
|
||
if (CC_P (regno) && src_code == COMPARE)
|
||
skip_nested_if = TRUE;
|
||
|
||
else if (CR_P (regno)
|
||
&& (src_code == IF_THEN_ELSE
|
||
|| GET_RTX_CLASS (src_code) == '<'))
|
||
skip_nested_if = TRUE;
|
||
}
|
||
}
|
||
|
||
if (! skip_nested_if)
|
||
for_each_rtx (&PATTERN (insn), frv_clear_registers_used,
|
||
(void *)&frv_ifcvt.nested_cc_ok_rewrite);
|
||
}
|
||
|
||
if (insn == last_insn)
|
||
break;
|
||
|
||
insn = NEXT_INSN (insn);
|
||
}
|
||
}
|
||
|
||
/* If this is a nested if, rewrite the CC registers that are available to
|
||
include the ones that can be rewritten, to increase the chance of being
|
||
able to allocate a paired CC/CR register combination. */
|
||
if (ce_info->pass > 1)
|
||
{
|
||
for (j = CC_FIRST; j <= CC_LAST; j++)
|
||
if (TEST_HARD_REG_BIT (frv_ifcvt.nested_cc_ok_rewrite, j))
|
||
SET_HARD_REG_BIT (tmp_reg->regs, j);
|
||
else
|
||
CLEAR_HARD_REG_BIT (tmp_reg->regs, j);
|
||
}
|
||
|
||
if (rtl_dump_file)
|
||
{
|
||
int num_gprs = 0;
|
||
fprintf (rtl_dump_file, "Available GPRs: ");
|
||
|
||
for (j = GPR_FIRST; j <= GPR_LAST; j++)
|
||
if (TEST_HARD_REG_BIT (tmp_reg->regs, j))
|
||
{
|
||
fprintf (rtl_dump_file, " %d [%s]", j, reg_names[j]);
|
||
if (++num_gprs > GPR_TEMP_NUM+2)
|
||
break;
|
||
}
|
||
|
||
fprintf (rtl_dump_file, "%s\nAvailable CRs: ",
|
||
(num_gprs > GPR_TEMP_NUM+2) ? " ..." : "");
|
||
|
||
for (j = CR_FIRST; j <= CR_LAST; j++)
|
||
if (TEST_HARD_REG_BIT (tmp_reg->regs, j))
|
||
fprintf (rtl_dump_file, " %d [%s]", j, reg_names[j]);
|
||
|
||
fputs ("\n", rtl_dump_file);
|
||
|
||
if (ce_info->pass > 1)
|
||
{
|
||
fprintf (rtl_dump_file, "Modifiable CCs: ");
|
||
for (j = CC_FIRST; j <= CC_LAST; j++)
|
||
if (TEST_HARD_REG_BIT (tmp_reg->regs, j))
|
||
fprintf (rtl_dump_file, " %d [%s]", j, reg_names[j]);
|
||
|
||
fprintf (rtl_dump_file, "\n%d nested COND_EXEC statements\n",
|
||
frv_ifcvt.num_nested_cond_exec);
|
||
}
|
||
}
|
||
|
||
/* Allocate the appropriate temporary condition code register. Try to
|
||
allocate the ICR/FCR register that corresponds to the ICC/FCC register so
|
||
that conditional cmp's can be done. */
|
||
if (mode == CCmode || mode == CC_UNSmode)
|
||
{
|
||
cr_class = ICR_REGS;
|
||
cc_class = ICC_REGS;
|
||
cc_first = ICC_FIRST;
|
||
cc_last = ICC_LAST;
|
||
}
|
||
else if (mode == CC_FPmode)
|
||
{
|
||
cr_class = FCR_REGS;
|
||
cc_class = FCC_REGS;
|
||
cc_first = FCC_FIRST;
|
||
cc_last = FCC_LAST;
|
||
}
|
||
else
|
||
{
|
||
cc_first = cc_last = 0;
|
||
cr_class = cc_class = NO_REGS;
|
||
}
|
||
|
||
cc = XEXP (true_expr, 0);
|
||
nested_cc = cr = NULL_RTX;
|
||
if (cc_class != NO_REGS)
|
||
{
|
||
/* For nested IFs and &&/||, see if we can find a CC and CR register pair
|
||
so we can execute a csubcc/caddcc/cfcmps instruction. */
|
||
int cc_regno;
|
||
|
||
for (cc_regno = cc_first; cc_regno <= cc_last; cc_regno++)
|
||
{
|
||
int cr_regno = cc_regno - CC_FIRST + CR_FIRST;
|
||
|
||
if (TEST_HARD_REG_BIT (frv_ifcvt.tmp_reg.regs, cc_regno)
|
||
&& TEST_HARD_REG_BIT (frv_ifcvt.tmp_reg.regs, cr_regno))
|
||
{
|
||
frv_ifcvt.tmp_reg.next_reg[ (int)cr_class ] = cr_regno;
|
||
cr = frv_alloc_temp_reg (tmp_reg, cr_class, CC_CCRmode, TRUE,
|
||
TRUE);
|
||
|
||
frv_ifcvt.tmp_reg.next_reg[ (int)cc_class ] = cc_regno;
|
||
nested_cc = frv_alloc_temp_reg (tmp_reg, cc_class, CCmode,
|
||
TRUE, TRUE);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (! cr)
|
||
{
|
||
if (rtl_dump_file)
|
||
fprintf (rtl_dump_file, "Could not allocate a CR temporary register\n");
|
||
|
||
goto fail;
|
||
}
|
||
|
||
if (rtl_dump_file)
|
||
fprintf (rtl_dump_file,
|
||
"Will use %s for conditional execution, %s for nested comparisons\n",
|
||
reg_names[ REGNO (cr)],
|
||
(nested_cc) ? reg_names[ REGNO (nested_cc) ] : "<none>");
|
||
|
||
/* Set the CCR bit. Note for integer tests, we reverse the condition so that
|
||
in an IF-THEN-ELSE sequence, we are testing the TRUE case against the CCR
|
||
bit being true. We don't do this for floating point, because of NaNs. */
|
||
code = GET_CODE (true_expr);
|
||
if (GET_MODE (cc) != CC_FPmode)
|
||
{
|
||
code = reverse_condition (code);
|
||
code_true = EQ;
|
||
code_false = NE;
|
||
}
|
||
else
|
||
{
|
||
code_true = NE;
|
||
code_false = EQ;
|
||
}
|
||
|
||
check_insn = gen_rtx_SET (VOIDmode, cr,
|
||
gen_rtx_fmt_ee (code, CC_CCRmode, cc, const0_rtx));
|
||
|
||
/* Record the check insn to be inserted later. */
|
||
frv_ifcvt_add_insn (check_insn, test_bb->end, TRUE);
|
||
|
||
/* Update the tests. */
|
||
frv_ifcvt.cr_reg = cr;
|
||
frv_ifcvt.nested_cc_reg = nested_cc;
|
||
*p_true = gen_rtx_fmt_ee (code_true, CC_CCRmode, cr, const0_rtx);
|
||
*p_false = gen_rtx_fmt_ee (code_false, CC_CCRmode, cr, const0_rtx);
|
||
return;
|
||
|
||
/* Fail, don't do this conditional execution. */
|
||
fail:
|
||
*p_true = NULL_RTX;
|
||
*p_false = NULL_RTX;
|
||
if (rtl_dump_file)
|
||
fprintf (rtl_dump_file, "Disabling this conditional execution.\n");
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
/* A C expression to modify the code described by the conditional if
|
||
information CE_INFO, for the basic block BB, possibly updating the tests in
|
||
TRUE_EXPR, and FALSE_EXPR for converting the && and || parts of if-then or
|
||
if-then-else code to conditional instructions. Set either TRUE_EXPR or
|
||
FALSE_EXPR to a null pointer if the tests cannot be converted. */
|
||
|
||
/* p_true and p_false are given expressions of the form:
|
||
|
||
(and (eq:CC_CCR (reg:CC_CCR)
|
||
(const_int 0))
|
||
(eq:CC (reg:CC)
|
||
(const_int 0))) */
|
||
|
||
void
|
||
frv_ifcvt_modify_multiple_tests (ce_info, bb, p_true, p_false)
|
||
ce_if_block_t *ce_info;
|
||
basic_block bb;
|
||
rtx *p_true;
|
||
rtx *p_false;
|
||
{
|
||
rtx old_true = XEXP (*p_true, 0);
|
||
rtx old_false = XEXP (*p_false, 0);
|
||
rtx true_expr = XEXP (*p_true, 1);
|
||
rtx false_expr = XEXP (*p_false, 1);
|
||
rtx test_expr;
|
||
rtx old_test;
|
||
rtx cr = XEXP (old_true, 0);
|
||
rtx check_insn;
|
||
rtx new_cr = NULL_RTX;
|
||
rtx *p_new_cr = (rtx *)0;
|
||
rtx if_else;
|
||
rtx compare;
|
||
rtx cc;
|
||
enum reg_class cr_class;
|
||
enum machine_mode mode = GET_MODE (true_expr);
|
||
rtx (*logical_func)(rtx, rtx, rtx);
|
||
|
||
if (TARGET_DEBUG_COND_EXEC)
|
||
{
|
||
fprintf (stderr,
|
||
"\n:::::::::: frv_ifcvt_modify_multiple_tests, before modification for %s\ntrue insn:\n",
|
||
ce_info->and_and_p ? "&&" : "||");
|
||
|
||
debug_rtx (*p_true);
|
||
|
||
fputs ("\nfalse insn:\n", stderr);
|
||
debug_rtx (*p_false);
|
||
}
|
||
|
||
if (TARGET_NO_MULTI_CE)
|
||
goto fail;
|
||
|
||
if (GET_CODE (cr) != REG)
|
||
goto fail;
|
||
|
||
if (mode == CCmode || mode == CC_UNSmode)
|
||
{
|
||
cr_class = ICR_REGS;
|
||
p_new_cr = &frv_ifcvt.extra_int_cr;
|
||
}
|
||
else if (mode == CC_FPmode)
|
||
{
|
||
cr_class = FCR_REGS;
|
||
p_new_cr = &frv_ifcvt.extra_fp_cr;
|
||
}
|
||
else
|
||
goto fail;
|
||
|
||
/* Allocate a temp CR, reusing a previously allocated temp CR if we have 3 or
|
||
more &&/|| tests. */
|
||
new_cr = *p_new_cr;
|
||
if (! new_cr)
|
||
{
|
||
new_cr = *p_new_cr = frv_alloc_temp_reg (&frv_ifcvt.tmp_reg, cr_class,
|
||
CC_CCRmode, TRUE, TRUE);
|
||
if (! new_cr)
|
||
goto fail;
|
||
}
|
||
|
||
if (ce_info->and_and_p)
|
||
{
|
||
old_test = old_false;
|
||
test_expr = true_expr;
|
||
logical_func = (GET_CODE (old_true) == EQ) ? gen_andcr : gen_andncr;
|
||
*p_true = gen_rtx_NE (CC_CCRmode, cr, const0_rtx);
|
||
*p_false = gen_rtx_EQ (CC_CCRmode, cr, const0_rtx);
|
||
}
|
||
else
|
||
{
|
||
old_test = old_false;
|
||
test_expr = false_expr;
|
||
logical_func = (GET_CODE (old_false) == EQ) ? gen_orcr : gen_orncr;
|
||
*p_true = gen_rtx_EQ (CC_CCRmode, cr, const0_rtx);
|
||
*p_false = gen_rtx_NE (CC_CCRmode, cr, const0_rtx);
|
||
}
|
||
|
||
/* First add the andcr/andncr/orcr/orncr, which will be added after the
|
||
conditional check instruction, due to frv_ifcvt_add_insn being a LIFO
|
||
stack. */
|
||
frv_ifcvt_add_insn ((*logical_func) (cr, cr, new_cr), bb->end, TRUE);
|
||
|
||
/* Now add the conditional check insn. */
|
||
cc = XEXP (test_expr, 0);
|
||
compare = gen_rtx_fmt_ee (GET_CODE (test_expr), CC_CCRmode, cc, const0_rtx);
|
||
if_else = gen_rtx_IF_THEN_ELSE (CC_CCRmode, old_test, compare, const0_rtx);
|
||
|
||
check_insn = gen_rtx_SET (VOIDmode, new_cr, if_else);
|
||
|
||
/* add the new check insn to the list of check insns that need to be
|
||
inserted. */
|
||
frv_ifcvt_add_insn (check_insn, bb->end, TRUE);
|
||
|
||
if (TARGET_DEBUG_COND_EXEC)
|
||
{
|
||
fputs ("\n:::::::::: frv_ifcvt_modify_multiple_tests, after modification\ntrue insn:\n",
|
||
stderr);
|
||
|
||
debug_rtx (*p_true);
|
||
|
||
fputs ("\nfalse insn:\n", stderr);
|
||
debug_rtx (*p_false);
|
||
}
|
||
|
||
return;
|
||
|
||
fail:
|
||
*p_true = *p_false = NULL_RTX;
|
||
|
||
/* If we allocated a CR register, release it. */
|
||
if (new_cr)
|
||
{
|
||
CLEAR_HARD_REG_BIT (frv_ifcvt.tmp_reg.regs, REGNO (new_cr));
|
||
*p_new_cr = NULL_RTX;
|
||
}
|
||
|
||
if (TARGET_DEBUG_COND_EXEC)
|
||
fputs ("\n:::::::::: frv_ifcvt_modify_multiple_tests, failed.\n", stderr);
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
/* Return a register which will be loaded with a value if an IF block is
|
||
converted to conditional execution. This is used to rewrite instructions
|
||
that use constants to ones that just use registers. */
|
||
|
||
static rtx
|
||
frv_ifcvt_load_value (value, insn)
|
||
rtx value;
|
||
rtx insn ATTRIBUTE_UNUSED;
|
||
{
|
||
int num_alloc = frv_ifcvt.cur_scratch_regs;
|
||
int i;
|
||
rtx reg;
|
||
|
||
/* We know gr0 == 0, so replace any errant uses. */
|
||
if (value == const0_rtx)
|
||
return gen_rtx_REG (SImode, GPR_FIRST);
|
||
|
||
/* First search all registers currently loaded to see if we have an
|
||
applicable constant. */
|
||
if (CONSTANT_P (value)
|
||
|| (GET_CODE (value) == REG && REGNO (value) == LR_REGNO))
|
||
{
|
||
for (i = 0; i < num_alloc; i++)
|
||
{
|
||
if (rtx_equal_p (SET_SRC (frv_ifcvt.scratch_regs[i]), value))
|
||
return SET_DEST (frv_ifcvt.scratch_regs[i]);
|
||
}
|
||
}
|
||
|
||
/* Have we exhausted the number of registers available? */
|
||
if (num_alloc >= GPR_TEMP_NUM)
|
||
{
|
||
if (rtl_dump_file)
|
||
fprintf (rtl_dump_file, "Too many temporary registers allocated\n");
|
||
|
||
return NULL_RTX;
|
||
}
|
||
|
||
/* Allocate the new register. */
|
||
reg = frv_alloc_temp_reg (&frv_ifcvt.tmp_reg, GPR_REGS, SImode, TRUE, TRUE);
|
||
if (! reg)
|
||
{
|
||
if (rtl_dump_file)
|
||
fputs ("Could not find a scratch register\n", rtl_dump_file);
|
||
|
||
return NULL_RTX;
|
||
}
|
||
|
||
frv_ifcvt.cur_scratch_regs++;
|
||
frv_ifcvt.scratch_regs[num_alloc] = gen_rtx_SET (VOIDmode, reg, value);
|
||
|
||
if (rtl_dump_file)
|
||
{
|
||
if (GET_CODE (value) == CONST_INT)
|
||
fprintf (rtl_dump_file, "Register %s will hold %ld\n",
|
||
reg_names[ REGNO (reg)], (long)INTVAL (value));
|
||
|
||
else if (GET_CODE (value) == REG && REGNO (value) == LR_REGNO)
|
||
fprintf (rtl_dump_file, "Register %s will hold LR\n",
|
||
reg_names[ REGNO (reg)]);
|
||
|
||
else
|
||
fprintf (rtl_dump_file, "Register %s will hold a saved value\n",
|
||
reg_names[ REGNO (reg)]);
|
||
}
|
||
|
||
return reg;
|
||
}
|
||
|
||
|
||
/* Update a MEM used in conditional code that might contain an offset to put
|
||
the offset into a scratch register, so that the conditional load/store
|
||
operations can be used. This function returns the original pointer if the
|
||
MEM is valid to use in conditional code, NULL if we can't load up the offset
|
||
into a temporary register, or the new MEM if we were successful. */
|
||
|
||
static rtx
|
||
frv_ifcvt_rewrite_mem (mem, mode, insn)
|
||
rtx mem;
|
||
enum machine_mode mode;
|
||
rtx insn;
|
||
{
|
||
rtx addr = XEXP (mem, 0);
|
||
|
||
if (!frv_legitimate_address_p (mode, addr, reload_completed, TRUE))
|
||
{
|
||
if (GET_CODE (addr) == PLUS)
|
||
{
|
||
rtx addr_op0 = XEXP (addr, 0);
|
||
rtx addr_op1 = XEXP (addr, 1);
|
||
|
||
if (plus_small_data_p (addr_op0, addr_op1))
|
||
addr = frv_ifcvt_load_value (addr, insn);
|
||
|
||
else if (GET_CODE (addr_op0) == REG && CONSTANT_P (addr_op1))
|
||
{
|
||
rtx reg = frv_ifcvt_load_value (addr_op1, insn);
|
||
if (!reg)
|
||
return NULL_RTX;
|
||
|
||
addr = gen_rtx_PLUS (Pmode, addr_op0, reg);
|
||
}
|
||
|
||
else
|
||
return NULL_RTX;
|
||
}
|
||
|
||
else if (CONSTANT_P (addr))
|
||
addr = frv_ifcvt_load_value (addr, insn);
|
||
|
||
else
|
||
return NULL_RTX;
|
||
|
||
if (addr == NULL_RTX)
|
||
return NULL_RTX;
|
||
|
||
else if (XEXP (mem, 0) != addr)
|
||
return change_address (mem, mode, addr);
|
||
}
|
||
|
||
return mem;
|
||
}
|
||
|
||
|
||
/* Given a PATTERN, return a SET expression if this PATTERN has only a single
|
||
SET, possibly conditionally executed. It may also have CLOBBERs, USEs. */
|
||
|
||
static rtx
|
||
single_set_pattern (pattern)
|
||
rtx pattern;
|
||
{
|
||
rtx set;
|
||
int i;
|
||
|
||
if (GET_CODE (pattern) == COND_EXEC)
|
||
pattern = COND_EXEC_CODE (pattern);
|
||
|
||
if (GET_CODE (pattern) == SET)
|
||
return pattern;
|
||
|
||
else if (GET_CODE (pattern) == PARALLEL)
|
||
{
|
||
for (i = 0, set = 0; i < XVECLEN (pattern, 0); i++)
|
||
{
|
||
rtx sub = XVECEXP (pattern, 0, i);
|
||
|
||
switch (GET_CODE (sub))
|
||
{
|
||
case USE:
|
||
case CLOBBER:
|
||
break;
|
||
|
||
case SET:
|
||
if (set)
|
||
return 0;
|
||
else
|
||
set = sub;
|
||
break;
|
||
|
||
default:
|
||
return 0;
|
||
}
|
||
}
|
||
return set;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
/* A C expression to modify the code described by the conditional if
|
||
information CE_INFO with the new PATTERN in INSN. If PATTERN is a null
|
||
pointer after the IFCVT_MODIFY_INSN macro executes, it is assumed that that
|
||
insn cannot be converted to be executed conditionally. */
|
||
|
||
rtx
|
||
frv_ifcvt_modify_insn (ce_info, pattern, insn)
|
||
ce_if_block_t *ce_info ATTRIBUTE_UNUSED;
|
||
rtx pattern;
|
||
rtx insn;
|
||
{
|
||
rtx orig_ce_pattern = pattern;
|
||
rtx set;
|
||
rtx op0;
|
||
rtx op1;
|
||
rtx test;
|
||
|
||
if (GET_CODE (pattern) != COND_EXEC)
|
||
abort ();
|
||
|
||
test = COND_EXEC_TEST (pattern);
|
||
if (GET_CODE (test) == AND)
|
||
{
|
||
rtx cr = frv_ifcvt.cr_reg;
|
||
rtx test_reg;
|
||
|
||
op0 = XEXP (test, 0);
|
||
if (! rtx_equal_p (cr, XEXP (op0, 0)))
|
||
goto fail;
|
||
|
||
op1 = XEXP (test, 1);
|
||
test_reg = XEXP (op1, 0);
|
||
if (GET_CODE (test_reg) != REG)
|
||
goto fail;
|
||
|
||
/* Is this the first nested if block in this sequence? If so, generate
|
||
an andcr or andncr. */
|
||
if (! frv_ifcvt.last_nested_if_cr)
|
||
{
|
||
rtx and_op;
|
||
|
||
frv_ifcvt.last_nested_if_cr = test_reg;
|
||
if (GET_CODE (op0) == NE)
|
||
and_op = gen_andcr (test_reg, cr, test_reg);
|
||
else
|
||
and_op = gen_andncr (test_reg, cr, test_reg);
|
||
|
||
frv_ifcvt_add_insn (and_op, insn, TRUE);
|
||
}
|
||
|
||
/* If this isn't the first statement in the nested if sequence, see if we
|
||
are dealing with the same register. */
|
||
else if (! rtx_equal_p (test_reg, frv_ifcvt.last_nested_if_cr))
|
||
goto fail;
|
||
|
||
COND_EXEC_TEST (pattern) = test = op1;
|
||
}
|
||
|
||
/* If this isn't a nested if, reset state variables. */
|
||
else
|
||
{
|
||
frv_ifcvt.last_nested_if_cr = NULL_RTX;
|
||
}
|
||
|
||
set = single_set_pattern (pattern);
|
||
if (set)
|
||
{
|
||
rtx dest = SET_DEST (set);
|
||
rtx src = SET_SRC (set);
|
||
enum machine_mode mode = GET_MODE (dest);
|
||
|
||
/* Check for normal binary operators */
|
||
if (mode == SImode
|
||
&& (GET_RTX_CLASS (GET_CODE (src)) == '2'
|
||
|| GET_RTX_CLASS (GET_CODE (src)) == 'c'))
|
||
{
|
||
op0 = XEXP (src, 0);
|
||
op1 = XEXP (src, 1);
|
||
|
||
/* Special case load of small data address which looks like:
|
||
r16+symbol_ref */
|
||
if (GET_CODE (src) == PLUS && plus_small_data_p (op0, op1))
|
||
{
|
||
src = frv_ifcvt_load_value (src, insn);
|
||
if (src)
|
||
COND_EXEC_CODE (pattern) = gen_rtx_SET (VOIDmode, dest, src);
|
||
else
|
||
goto fail;
|
||
}
|
||
|
||
else if (integer_register_operand (op0, SImode) && CONSTANT_P (op1))
|
||
{
|
||
op1 = frv_ifcvt_load_value (op1, insn);
|
||
if (op1)
|
||
COND_EXEC_CODE (pattern)
|
||
= gen_rtx_SET (VOIDmode, dest, gen_rtx_fmt_ee (GET_CODE (src),
|
||
GET_MODE (src),
|
||
op0, op1));
|
||
else
|
||
goto fail;
|
||
}
|
||
}
|
||
|
||
/* For multiply by a constant, we need to handle the sign extending
|
||
correctly. Add a USE of the value after the multiply to prevent flow
|
||
from cratering because only one register out of the two were used. */
|
||
else if (mode == DImode && GET_CODE (src) == MULT)
|
||
{
|
||
op0 = XEXP (src, 0);
|
||
op1 = XEXP (src, 1);
|
||
if (GET_CODE (op0) == SIGN_EXTEND && GET_CODE (op1) == CONST_INT)
|
||
{
|
||
op1 = frv_ifcvt_load_value (op1, insn);
|
||
if (op1)
|
||
{
|
||
op1 = gen_rtx_SIGN_EXTEND (DImode, op1);
|
||
COND_EXEC_CODE (pattern)
|
||
= gen_rtx_SET (VOIDmode, dest,
|
||
gen_rtx_MULT (DImode, op0, op1));
|
||
}
|
||
else
|
||
goto fail;
|
||
}
|
||
|
||
frv_ifcvt_add_insn (gen_rtx_USE (VOIDmode, dest), insn, FALSE);
|
||
}
|
||
|
||
/* If we are just loading a constant created for a nested conditional
|
||
execution statement, just load the constant without any conditional
|
||
execution, since we know that the constant will not interfere with any
|
||
other registers. */
|
||
else if (frv_ifcvt.scratch_insns_bitmap
|
||
&& bitmap_bit_p (frv_ifcvt.scratch_insns_bitmap,
|
||
INSN_UID (insn)))
|
||
pattern = set;
|
||
|
||
else if (mode == QImode || mode == HImode || mode == SImode
|
||
|| mode == SFmode)
|
||
{
|
||
int changed_p = FALSE;
|
||
|
||
/* Check for just loading up a constant */
|
||
if (CONSTANT_P (src) && integer_register_operand (dest, mode))
|
||
{
|
||
src = frv_ifcvt_load_value (src, insn);
|
||
if (!src)
|
||
goto fail;
|
||
|
||
changed_p = TRUE;
|
||
}
|
||
|
||
/* See if we need to fix up stores */
|
||
if (GET_CODE (dest) == MEM)
|
||
{
|
||
rtx new_mem = frv_ifcvt_rewrite_mem (dest, mode, insn);
|
||
|
||
if (!new_mem)
|
||
goto fail;
|
||
|
||
else if (new_mem != dest)
|
||
{
|
||
changed_p = TRUE;
|
||
dest = new_mem;
|
||
}
|
||
}
|
||
|
||
/* See if we need to fix up loads */
|
||
if (GET_CODE (src) == MEM)
|
||
{
|
||
rtx new_mem = frv_ifcvt_rewrite_mem (src, mode, insn);
|
||
|
||
if (!new_mem)
|
||
goto fail;
|
||
|
||
else if (new_mem != src)
|
||
{
|
||
changed_p = TRUE;
|
||
src = new_mem;
|
||
}
|
||
}
|
||
|
||
/* If either src or destination changed, redo SET. */
|
||
if (changed_p)
|
||
COND_EXEC_CODE (pattern) = gen_rtx_SET (VOIDmode, dest, src);
|
||
}
|
||
|
||
/* Rewrite a nested set cccr in terms of IF_THEN_ELSE. Also deal with
|
||
rewriting the CC register to be the same as the paired CC/CR register
|
||
for nested ifs. */
|
||
else if (mode == CC_CCRmode && GET_RTX_CLASS (GET_CODE (src)) == '<')
|
||
{
|
||
int regno = REGNO (XEXP (src, 0));
|
||
rtx if_else;
|
||
|
||
if (ce_info->pass > 1
|
||
&& regno != (int)REGNO (frv_ifcvt.nested_cc_reg)
|
||
&& TEST_HARD_REG_BIT (frv_ifcvt.nested_cc_ok_rewrite, regno))
|
||
{
|
||
src = gen_rtx_fmt_ee (GET_CODE (src),
|
||
CC_CCRmode,
|
||
frv_ifcvt.nested_cc_reg,
|
||
XEXP (src, 1));
|
||
}
|
||
|
||
if_else = gen_rtx_IF_THEN_ELSE (CC_CCRmode, test, src, const0_rtx);
|
||
pattern = gen_rtx_SET (VOIDmode, dest, if_else);
|
||
}
|
||
|
||
/* Remap a nested compare instruction to use the paired CC/CR reg. */
|
||
else if (ce_info->pass > 1
|
||
&& GET_CODE (dest) == REG
|
||
&& CC_P (REGNO (dest))
|
||
&& REGNO (dest) != REGNO (frv_ifcvt.nested_cc_reg)
|
||
&& TEST_HARD_REG_BIT (frv_ifcvt.nested_cc_ok_rewrite,
|
||
REGNO (dest))
|
||
&& GET_CODE (src) == COMPARE)
|
||
{
|
||
PUT_MODE (frv_ifcvt.nested_cc_reg, GET_MODE (dest));
|
||
COND_EXEC_CODE (pattern)
|
||
= gen_rtx_SET (VOIDmode, frv_ifcvt.nested_cc_reg, copy_rtx (src));
|
||
}
|
||
}
|
||
|
||
if (TARGET_DEBUG_COND_EXEC)
|
||
{
|
||
rtx orig_pattern = PATTERN (insn);
|
||
|
||
PATTERN (insn) = pattern;
|
||
fprintf (stderr,
|
||
"\n:::::::::: frv_ifcvt_modify_insn: pass = %d, insn after modification:\n",
|
||
ce_info->pass);
|
||
|
||
debug_rtx (insn);
|
||
PATTERN (insn) = orig_pattern;
|
||
}
|
||
|
||
return pattern;
|
||
|
||
fail:
|
||
if (TARGET_DEBUG_COND_EXEC)
|
||
{
|
||
rtx orig_pattern = PATTERN (insn);
|
||
|
||
PATTERN (insn) = orig_ce_pattern;
|
||
fprintf (stderr,
|
||
"\n:::::::::: frv_ifcvt_modify_insn: pass = %d, insn could not be modified:\n",
|
||
ce_info->pass);
|
||
|
||
debug_rtx (insn);
|
||
PATTERN (insn) = orig_pattern;
|
||
}
|
||
|
||
return NULL_RTX;
|
||
}
|
||
|
||
|
||
/* A C expression to perform any final machine dependent modifications in
|
||
converting code to conditional execution in the code described by the
|
||
conditional if information CE_INFO. */
|
||
|
||
void
|
||
frv_ifcvt_modify_final (ce_info)
|
||
ce_if_block_t *ce_info ATTRIBUTE_UNUSED;
|
||
{
|
||
rtx existing_insn;
|
||
rtx check_insn;
|
||
rtx p = frv_ifcvt.added_insns_list;
|
||
int i;
|
||
|
||
/* Loop inserting the check insns. The last check insn is the first test,
|
||
and is the appropriate place to insert constants. */
|
||
if (! p)
|
||
abort ();
|
||
|
||
do
|
||
{
|
||
rtx check_and_insert_insns = XEXP (p, 0);
|
||
rtx old_p = p;
|
||
|
||
check_insn = XEXP (check_and_insert_insns, 0);
|
||
existing_insn = XEXP (check_and_insert_insns, 1);
|
||
p = XEXP (p, 1);
|
||
|
||
/* The jump bit is used to say that the new insn is to be inserted BEFORE
|
||
the existing insn, otherwise it is to be inserted AFTER. */
|
||
if (check_and_insert_insns->jump)
|
||
{
|
||
emit_insn_before (check_insn, existing_insn);
|
||
check_and_insert_insns->jump = 0;
|
||
}
|
||
else
|
||
emit_insn_after (check_insn, existing_insn);
|
||
|
||
free_EXPR_LIST_node (check_and_insert_insns);
|
||
free_EXPR_LIST_node (old_p);
|
||
}
|
||
while (p != NULL_RTX);
|
||
|
||
/* Load up any constants needed into temp gprs */
|
||
for (i = 0; i < frv_ifcvt.cur_scratch_regs; i++)
|
||
{
|
||
rtx insn = emit_insn_before (frv_ifcvt.scratch_regs[i], existing_insn);
|
||
if (! frv_ifcvt.scratch_insns_bitmap)
|
||
frv_ifcvt.scratch_insns_bitmap = BITMAP_XMALLOC ();
|
||
bitmap_set_bit (frv_ifcvt.scratch_insns_bitmap, INSN_UID (insn));
|
||
frv_ifcvt.scratch_regs[i] = NULL_RTX;
|
||
}
|
||
|
||
frv_ifcvt.added_insns_list = NULL_RTX;
|
||
frv_ifcvt.cur_scratch_regs = 0;
|
||
}
|
||
|
||
|
||
/* A C expression to cancel any machine dependent modifications in converting
|
||
code to conditional execution in the code described by the conditional if
|
||
information CE_INFO. */
|
||
|
||
void
|
||
frv_ifcvt_modify_cancel (ce_info)
|
||
ce_if_block_t *ce_info ATTRIBUTE_UNUSED;
|
||
{
|
||
int i;
|
||
rtx p = frv_ifcvt.added_insns_list;
|
||
|
||
/* Loop freeing up the EXPR_LIST's allocated. */
|
||
while (p != NULL_RTX)
|
||
{
|
||
rtx check_and_jump = XEXP (p, 0);
|
||
rtx old_p = p;
|
||
|
||
p = XEXP (p, 1);
|
||
free_EXPR_LIST_node (check_and_jump);
|
||
free_EXPR_LIST_node (old_p);
|
||
}
|
||
|
||
/* Release any temporary gprs allocated. */
|
||
for (i = 0; i < frv_ifcvt.cur_scratch_regs; i++)
|
||
frv_ifcvt.scratch_regs[i] = NULL_RTX;
|
||
|
||
frv_ifcvt.added_insns_list = NULL_RTX;
|
||
frv_ifcvt.cur_scratch_regs = 0;
|
||
return;
|
||
}
|
||
|
||
/* A C expression for the size in bytes of the trampoline, as an integer.
|
||
The template is:
|
||
|
||
setlo #0, <jmp_reg>
|
||
setlo #0, <static_chain>
|
||
sethi #0, <jmp_reg>
|
||
sethi #0, <static_chain>
|
||
jmpl @(gr0,<jmp_reg>) */
|
||
|
||
int
|
||
frv_trampoline_size ()
|
||
{
|
||
return 5 /* instructions */ * 4 /* instruction size */;
|
||
}
|
||
|
||
|
||
/* A C statement to initialize the variable parts of a trampoline. ADDR is an
|
||
RTX for the address of the trampoline; FNADDR is an RTX for the address of
|
||
the nested function; STATIC_CHAIN is an RTX for the static chain value that
|
||
should be passed to the function when it is called.
|
||
|
||
The template is:
|
||
|
||
setlo #0, <jmp_reg>
|
||
setlo #0, <static_chain>
|
||
sethi #0, <jmp_reg>
|
||
sethi #0, <static_chain>
|
||
jmpl @(gr0,<jmp_reg>) */
|
||
|
||
void
|
||
frv_initialize_trampoline (addr, fnaddr, static_chain)
|
||
rtx addr;
|
||
rtx fnaddr;
|
||
rtx static_chain;
|
||
{
|
||
rtx sc_reg = force_reg (Pmode, static_chain);
|
||
|
||
emit_library_call (gen_rtx_SYMBOL_REF (SImode, "__trampoline_setup"),
|
||
FALSE, VOIDmode, 4,
|
||
addr, Pmode,
|
||
GEN_INT (frv_trampoline_size ()), SImode,
|
||
fnaddr, Pmode,
|
||
sc_reg, Pmode);
|
||
}
|
||
|
||
|
||
/* Many machines have some registers that cannot be copied directly to or from
|
||
memory or even from other types of registers. An example is the `MQ'
|
||
register, which on most machines, can only be copied to or from general
|
||
registers, but not memory. Some machines allow copying all registers to and
|
||
from memory, but require a scratch register for stores to some memory
|
||
locations (e.g., those with symbolic address on the RT, and those with
|
||
certain symbolic address on the SPARC when compiling PIC). In some cases,
|
||
both an intermediate and a scratch register are required.
|
||
|
||
You should define these macros to indicate to the reload phase that it may
|
||
need to allocate at least one register for a reload in addition to the
|
||
register to contain the data. Specifically, if copying X to a register
|
||
CLASS in MODE requires an intermediate register, you should define
|
||
`SECONDARY_INPUT_RELOAD_CLASS' to return the largest register class all of
|
||
whose registers can be used as intermediate registers or scratch registers.
|
||
|
||
If copying a register CLASS in MODE to X requires an intermediate or scratch
|
||
register, `SECONDARY_OUTPUT_RELOAD_CLASS' should be defined to return the
|
||
largest register class required. If the requirements for input and output
|
||
reloads are the same, the macro `SECONDARY_RELOAD_CLASS' should be used
|
||
instead of defining both macros identically.
|
||
|
||
The values returned by these macros are often `GENERAL_REGS'. Return
|
||
`NO_REGS' if no spare register is needed; i.e., if X can be directly copied
|
||
to or from a register of CLASS in MODE without requiring a scratch register.
|
||
Do not define this macro if it would always return `NO_REGS'.
|
||
|
||
If a scratch register is required (either with or without an intermediate
|
||
register), you should define patterns for `reload_inM' or `reload_outM', as
|
||
required.. These patterns, which will normally be implemented with a
|
||
`define_expand', should be similar to the `movM' patterns, except that
|
||
operand 2 is the scratch register.
|
||
|
||
Define constraints for the reload register and scratch register that contain
|
||
a single register class. If the original reload register (whose class is
|
||
CLASS) can meet the constraint given in the pattern, the value returned by
|
||
these macros is used for the class of the scratch register. Otherwise, two
|
||
additional reload registers are required. Their classes are obtained from
|
||
the constraints in the insn pattern.
|
||
|
||
X might be a pseudo-register or a `subreg' of a pseudo-register, which could
|
||
either be in a hard register or in memory. Use `true_regnum' to find out;
|
||
it will return -1 if the pseudo is in memory and the hard register number if
|
||
it is in a register.
|
||
|
||
These macros should not be used in the case where a particular class of
|
||
registers can only be copied to memory and not to another class of
|
||
registers. In that case, secondary reload registers are not needed and
|
||
would not be helpful. Instead, a stack location must be used to perform the
|
||
copy and the `movM' pattern should use memory as an intermediate storage.
|
||
This case often occurs between floating-point and general registers. */
|
||
|
||
enum reg_class
|
||
frv_secondary_reload_class (class, mode, x, in_p)
|
||
enum reg_class class;
|
||
enum machine_mode mode ATTRIBUTE_UNUSED;
|
||
rtx x;
|
||
int in_p ATTRIBUTE_UNUSED;
|
||
{
|
||
enum reg_class ret;
|
||
|
||
switch (class)
|
||
{
|
||
default:
|
||
ret = NO_REGS;
|
||
break;
|
||
|
||
/* Accumulators/Accumulator guard registers need to go through floating
|
||
point registers. */
|
||
case QUAD_REGS:
|
||
case EVEN_REGS:
|
||
case GPR_REGS:
|
||
ret = NO_REGS;
|
||
if (x && GET_CODE (x) == REG)
|
||
{
|
||
int regno = REGNO (x);
|
||
|
||
if (ACC_P (regno) || ACCG_P (regno))
|
||
ret = FPR_REGS;
|
||
}
|
||
break;
|
||
|
||
/* Nonzero constants should be loaded into an FPR through a GPR. */
|
||
case QUAD_FPR_REGS:
|
||
case FEVEN_REGS:
|
||
case FPR_REGS:
|
||
if (x && CONSTANT_P (x) && !ZERO_P (x))
|
||
ret = GPR_REGS;
|
||
else
|
||
ret = NO_REGS;
|
||
break;
|
||
|
||
/* All of these types need gpr registers. */
|
||
case ICC_REGS:
|
||
case FCC_REGS:
|
||
case CC_REGS:
|
||
case ICR_REGS:
|
||
case FCR_REGS:
|
||
case CR_REGS:
|
||
case LCR_REG:
|
||
case LR_REG:
|
||
ret = GPR_REGS;
|
||
break;
|
||
|
||
/* The accumulators need fpr registers */
|
||
case ACC_REGS:
|
||
case EVEN_ACC_REGS:
|
||
case QUAD_ACC_REGS:
|
||
case ACCG_REGS:
|
||
ret = FPR_REGS;
|
||
break;
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
|
||
/* A C expression whose value is nonzero if pseudos that have been assigned to
|
||
registers of class CLASS would likely be spilled because registers of CLASS
|
||
are needed for spill registers.
|
||
|
||
The default value of this macro returns 1 if CLASS has exactly one register
|
||
and zero otherwise. On most machines, this default should be used. Only
|
||
define this macro to some other expression if pseudo allocated by
|
||
`local-alloc.c' end up in memory because their hard registers were needed
|
||
for spill registers. If this macro returns nonzero for those classes, those
|
||
pseudos will only be allocated by `global.c', which knows how to reallocate
|
||
the pseudo to another register. If there would not be another register
|
||
available for reallocation, you should not change the definition of this
|
||
macro since the only effect of such a definition would be to slow down
|
||
register allocation. */
|
||
|
||
int
|
||
frv_class_likely_spilled_p (class)
|
||
enum reg_class class;
|
||
{
|
||
switch (class)
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case ICC_REGS:
|
||
case FCC_REGS:
|
||
case CC_REGS:
|
||
case ICR_REGS:
|
||
case FCR_REGS:
|
||
case CR_REGS:
|
||
case LCR_REG:
|
||
case LR_REG:
|
||
case SPR_REGS:
|
||
case QUAD_ACC_REGS:
|
||
case EVEN_ACC_REGS:
|
||
case ACC_REGS:
|
||
case ACCG_REGS:
|
||
return TRUE;
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
/* An expression for the alignment of a structure field FIELD if the
|
||
alignment computed in the usual way is COMPUTED. GNU CC uses this
|
||
value instead of the value in `BIGGEST_ALIGNMENT' or
|
||
`BIGGEST_FIELD_ALIGNMENT', if defined, for structure fields only. */
|
||
|
||
/* The definition type of the bit field data is either char, short, long or
|
||
long long. The maximum bit size is the number of bits of its own type.
|
||
|
||
The bit field data is assigned to a storage unit that has an adequate size
|
||
for bit field data retention and is located at the smallest address.
|
||
|
||
Consecutive bit field data are packed at consecutive bits having the same
|
||
storage unit, with regard to the type, beginning with the MSB and continuing
|
||
toward the LSB.
|
||
|
||
If a field to be assigned lies over a bit field type boundary, its
|
||
assignment is completed by aligning it with a boundary suitable for the
|
||
type.
|
||
|
||
When a bit field having a bit length of 0 is declared, it is forcibly
|
||
assigned to the next storage unit.
|
||
|
||
e.g)
|
||
struct {
|
||
int a:2;
|
||
int b:6;
|
||
char c:4;
|
||
int d:10;
|
||
int :0;
|
||
int f:2;
|
||
} x;
|
||
|
||
+0 +1 +2 +3
|
||
&x 00000000 00000000 00000000 00000000
|
||
MLM----L
|
||
a b
|
||
&x+4 00000000 00000000 00000000 00000000
|
||
M--L
|
||
c
|
||
&x+8 00000000 00000000 00000000 00000000
|
||
M----------L
|
||
d
|
||
&x+12 00000000 00000000 00000000 00000000
|
||
ML
|
||
f
|
||
*/
|
||
|
||
int
|
||
frv_adjust_field_align (field, computed)
|
||
tree field;
|
||
int computed;
|
||
{
|
||
/* C++ provides a null DECL_CONTEXT if the bit field is wider than its
|
||
type. */
|
||
if (DECL_BIT_FIELD (field) && DECL_CONTEXT (field))
|
||
{
|
||
tree parent = DECL_CONTEXT (field);
|
||
tree prev = NULL_TREE;
|
||
tree cur;
|
||
|
||
/* Loop finding the previous field to the current one */
|
||
for (cur = TYPE_FIELDS (parent); cur && cur != field; cur = TREE_CHAIN (cur))
|
||
{
|
||
if (TREE_CODE (cur) != FIELD_DECL)
|
||
continue;
|
||
|
||
prev = cur;
|
||
}
|
||
|
||
if (!cur)
|
||
abort ();
|
||
|
||
/* If this isn't a :0 field and if the previous element is a bitfield
|
||
also, see if the type is different, if so, we will need to align the
|
||
bit-field to the next boundary */
|
||
if (prev
|
||
&& ! DECL_PACKED (field)
|
||
&& ! integer_zerop (DECL_SIZE (field))
|
||
&& DECL_BIT_FIELD_TYPE (field) != DECL_BIT_FIELD_TYPE (prev))
|
||
{
|
||
int prev_align = TYPE_ALIGN (TREE_TYPE (prev));
|
||
int cur_align = TYPE_ALIGN (TREE_TYPE (field));
|
||
computed = (prev_align > cur_align) ? prev_align : cur_align;
|
||
}
|
||
}
|
||
|
||
return computed;
|
||
}
|
||
|
||
|
||
/* A C expression that is nonzero if it is permissible to store a value of mode
|
||
MODE in hard register number REGNO (or in several registers starting with
|
||
that one). For a machine where all registers are equivalent, a suitable
|
||
definition is
|
||
|
||
#define HARD_REGNO_MODE_OK(REGNO, MODE) 1
|
||
|
||
It is not necessary for this macro to check for the numbers of fixed
|
||
registers, because the allocation mechanism considers them to be always
|
||
occupied.
|
||
|
||
On some machines, double-precision values must be kept in even/odd register
|
||
pairs. The way to implement that is to define this macro to reject odd
|
||
register numbers for such modes.
|
||
|
||
The minimum requirement for a mode to be OK in a register is that the
|
||
`movMODE' instruction pattern support moves between the register and any
|
||
other hard register for which the mode is OK; and that moving a value into
|
||
the register and back out not alter it.
|
||
|
||
Since the same instruction used to move `SImode' will work for all narrower
|
||
integer modes, it is not necessary on any machine for `HARD_REGNO_MODE_OK'
|
||
to distinguish between these modes, provided you define patterns `movhi',
|
||
etc., to take advantage of this. This is useful because of the interaction
|
||
between `HARD_REGNO_MODE_OK' and `MODES_TIEABLE_P'; it is very desirable for
|
||
all integer modes to be tieable.
|
||
|
||
Many machines have special registers for floating point arithmetic. Often
|
||
people assume that floating point machine modes are allowed only in floating
|
||
point registers. This is not true. Any registers that can hold integers
|
||
can safely *hold* a floating point machine mode, whether or not floating
|
||
arithmetic can be done on it in those registers. Integer move instructions
|
||
can be used to move the values.
|
||
|
||
On some machines, though, the converse is true: fixed-point machine modes
|
||
may not go in floating registers. This is true if the floating registers
|
||
normalize any value stored in them, because storing a non-floating value
|
||
there would garble it. In this case, `HARD_REGNO_MODE_OK' should reject
|
||
fixed-point machine modes in floating registers. But if the floating
|
||
registers do not automatically normalize, if you can store any bit pattern
|
||
in one and retrieve it unchanged without a trap, then any machine mode may
|
||
go in a floating register, so you can define this macro to say so.
|
||
|
||
The primary significance of special floating registers is rather that they
|
||
are the registers acceptable in floating point arithmetic instructions.
|
||
However, this is of no concern to `HARD_REGNO_MODE_OK'. You handle it by
|
||
writing the proper constraints for those instructions.
|
||
|
||
On some machines, the floating registers are especially slow to access, so
|
||
that it is better to store a value in a stack frame than in such a register
|
||
if floating point arithmetic is not being done. As long as the floating
|
||
registers are not in class `GENERAL_REGS', they will not be used unless some
|
||
pattern's constraint asks for one. */
|
||
|
||
int
|
||
frv_hard_regno_mode_ok (regno, mode)
|
||
int regno;
|
||
enum machine_mode mode;
|
||
{
|
||
int base;
|
||
int mask;
|
||
|
||
switch (mode)
|
||
{
|
||
case CCmode:
|
||
case CC_UNSmode:
|
||
return ICC_P (regno) || GPR_P (regno);
|
||
|
||
case CC_CCRmode:
|
||
return CR_P (regno) || GPR_P (regno);
|
||
|
||
case CC_FPmode:
|
||
return FCC_P (regno) || GPR_P (regno);
|
||
|
||
default:
|
||
break;
|
||
}
|
||
|
||
/* Set BASE to the first register in REGNO's class. Set MASK to the
|
||
bits that must be clear in (REGNO - BASE) for the register to be
|
||
well-aligned. */
|
||
if (INTEGRAL_MODE_P (mode) || FLOAT_MODE_P (mode) || VECTOR_MODE_P (mode))
|
||
{
|
||
if (ACCG_P (regno))
|
||
{
|
||
/* ACCGs store one byte. Two-byte quantities must start in
|
||
even-numbered registers, four-byte ones in registers whose
|
||
numbers are divisible by four, and so on. */
|
||
base = ACCG_FIRST;
|
||
mask = GET_MODE_SIZE (mode) - 1;
|
||
}
|
||
else
|
||
{
|
||
/* The other registers store one word. */
|
||
if (GPR_P (regno))
|
||
base = GPR_FIRST;
|
||
|
||
else if (FPR_P (regno))
|
||
base = FPR_FIRST;
|
||
|
||
else if (ACC_P (regno))
|
||
base = ACC_FIRST;
|
||
|
||
else
|
||
return 0;
|
||
|
||
/* Anything smaller than an SI is OK in any word-sized register. */
|
||
if (GET_MODE_SIZE (mode) < 4)
|
||
return 1;
|
||
|
||
mask = (GET_MODE_SIZE (mode) / 4) - 1;
|
||
}
|
||
return (((regno - base) & mask) == 0);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
/* A C expression for the number of consecutive hard registers, starting at
|
||
register number REGNO, required to hold a value of mode MODE.
|
||
|
||
On a machine where all registers are exactly one word, a suitable definition
|
||
of this macro is
|
||
|
||
#define HARD_REGNO_NREGS(REGNO, MODE) \
|
||
((GET_MODE_SIZE (MODE) + UNITS_PER_WORD - 1) \
|
||
/ UNITS_PER_WORD)) */
|
||
|
||
/* On the FRV, make the CC_FP mode take 3 words in the integer registers, so
|
||
that we can build the appropriate instructions to properly reload the
|
||
values. Also, make the byte-sized accumulator guards use one guard
|
||
for each byte. */
|
||
|
||
int
|
||
frv_hard_regno_nregs (regno, mode)
|
||
int regno;
|
||
enum machine_mode mode;
|
||
{
|
||
if (ACCG_P (regno))
|
||
return GET_MODE_SIZE (mode);
|
||
else
|
||
return (GET_MODE_SIZE (mode) + UNITS_PER_WORD - 1) / UNITS_PER_WORD;
|
||
}
|
||
|
||
|
||
/* A C expression for the maximum number of consecutive registers of
|
||
class CLASS needed to hold a value of mode MODE.
|
||
|
||
This is closely related to the macro `HARD_REGNO_NREGS'. In fact, the value
|
||
of the macro `CLASS_MAX_NREGS (CLASS, MODE)' should be the maximum value of
|
||
`HARD_REGNO_NREGS (REGNO, MODE)' for all REGNO values in the class CLASS.
|
||
|
||
This macro helps control the handling of multiple-word values in
|
||
the reload pass.
|
||
|
||
This declaration is required. */
|
||
|
||
int
|
||
frv_class_max_nregs (class, mode)
|
||
enum reg_class class;
|
||
enum machine_mode mode;
|
||
{
|
||
if (class == ACCG_REGS)
|
||
/* An N-byte value requires N accumulator guards. */
|
||
return GET_MODE_SIZE (mode);
|
||
else
|
||
return (GET_MODE_SIZE (mode) + UNITS_PER_WORD - 1) / UNITS_PER_WORD;
|
||
}
|
||
|
||
|
||
/* A C expression that is nonzero if X is a legitimate constant for an
|
||
immediate operand on the target machine. You can assume that X satisfies
|
||
`CONSTANT_P', so you need not check this. In fact, `1' is a suitable
|
||
definition for this macro on machines where anything `CONSTANT_P' is valid. */
|
||
|
||
int
|
||
frv_legitimate_constant_p (x)
|
||
rtx x;
|
||
{
|
||
enum machine_mode mode = GET_MODE (x);
|
||
|
||
/* All of the integer constants are ok */
|
||
if (GET_CODE (x) != CONST_DOUBLE)
|
||
return TRUE;
|
||
|
||
/* double integer constants are ok */
|
||
if (mode == VOIDmode || mode == DImode)
|
||
return TRUE;
|
||
|
||
/* 0 is always ok */
|
||
if (x == CONST0_RTX (mode))
|
||
return TRUE;
|
||
|
||
/* If floating point is just emulated, allow any constant, since it will be
|
||
constructed in the GPRs */
|
||
if (!TARGET_HAS_FPRS)
|
||
return TRUE;
|
||
|
||
if (mode == DFmode && !TARGET_DOUBLE)
|
||
return TRUE;
|
||
|
||
/* Otherwise store the constant away and do a load. */
|
||
return FALSE;
|
||
}
|
||
|
||
/* A C expression for the cost of moving data from a register in class FROM to
|
||
one in class TO. The classes are expressed using the enumeration values
|
||
such as `GENERAL_REGS'. A value of 4 is the default; other values are
|
||
interpreted relative to that.
|
||
|
||
It is not required that the cost always equal 2 when FROM is the same as TO;
|
||
on some machines it is expensive to move between registers if they are not
|
||
general registers.
|
||
|
||
If reload sees an insn consisting of a single `set' between two hard
|
||
registers, and if `REGISTER_MOVE_COST' applied to their classes returns a
|
||
value of 2, reload does not check to ensure that the constraints of the insn
|
||
are met. Setting a cost of other than 2 will allow reload to verify that
|
||
the constraints are met. You should do this if the `movM' pattern's
|
||
constraints do not allow such copying. */
|
||
|
||
#define HIGH_COST 40
|
||
#define MEDIUM_COST 3
|
||
#define LOW_COST 1
|
||
|
||
int
|
||
frv_register_move_cost (from, to)
|
||
enum reg_class from;
|
||
enum reg_class to;
|
||
{
|
||
switch (from)
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case QUAD_REGS:
|
||
case EVEN_REGS:
|
||
case GPR_REGS:
|
||
switch (to)
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case QUAD_REGS:
|
||
case EVEN_REGS:
|
||
case GPR_REGS:
|
||
return LOW_COST;
|
||
|
||
case FEVEN_REGS:
|
||
case FPR_REGS:
|
||
return LOW_COST;
|
||
|
||
case LCR_REG:
|
||
case LR_REG:
|
||
case SPR_REGS:
|
||
return LOW_COST;
|
||
}
|
||
|
||
case FEVEN_REGS:
|
||
case FPR_REGS:
|
||
switch (to)
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case QUAD_REGS:
|
||
case EVEN_REGS:
|
||
case GPR_REGS:
|
||
case ACC_REGS:
|
||
case EVEN_ACC_REGS:
|
||
case QUAD_ACC_REGS:
|
||
case ACCG_REGS:
|
||
return MEDIUM_COST;
|
||
|
||
case FEVEN_REGS:
|
||
case FPR_REGS:
|
||
return LOW_COST;
|
||
}
|
||
|
||
case LCR_REG:
|
||
case LR_REG:
|
||
case SPR_REGS:
|
||
switch (to)
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case QUAD_REGS:
|
||
case EVEN_REGS:
|
||
case GPR_REGS:
|
||
return MEDIUM_COST;
|
||
}
|
||
|
||
case ACC_REGS:
|
||
case EVEN_ACC_REGS:
|
||
case QUAD_ACC_REGS:
|
||
case ACCG_REGS:
|
||
switch (to)
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case FEVEN_REGS:
|
||
case FPR_REGS:
|
||
return MEDIUM_COST;
|
||
|
||
}
|
||
}
|
||
|
||
return HIGH_COST;
|
||
}
|
||
|
||
/* Implementation of TARGET_ASM_INTEGER. In the FRV case we need to
|
||
use ".picptr" to generate safe relocations for PIC code. We also
|
||
need a fixup entry for aligned (non-debugging) code. */
|
||
|
||
static bool
|
||
frv_assemble_integer (value, size, aligned_p)
|
||
rtx value;
|
||
unsigned int size;
|
||
int aligned_p;
|
||
{
|
||
if (flag_pic && size == UNITS_PER_WORD)
|
||
{
|
||
if (GET_CODE (value) == CONST
|
||
|| GET_CODE (value) == SYMBOL_REF
|
||
|| GET_CODE (value) == LABEL_REF)
|
||
{
|
||
if (aligned_p)
|
||
{
|
||
static int label_num = 0;
|
||
char buf[256];
|
||
const char *p;
|
||
|
||
ASM_GENERATE_INTERNAL_LABEL (buf, "LCP", label_num++);
|
||
p = (* targetm.strip_name_encoding) (buf);
|
||
|
||
fprintf (asm_out_file, "%s:\n", p);
|
||
fprintf (asm_out_file, "%s\n", FIXUP_SECTION_ASM_OP);
|
||
fprintf (asm_out_file, "\t.picptr\t%s\n", p);
|
||
fprintf (asm_out_file, "\t.previous\n");
|
||
}
|
||
assemble_integer_with_op ("\t.picptr\t", value);
|
||
return true;
|
||
}
|
||
if (!aligned_p)
|
||
{
|
||
/* We've set the unaligned SI op to NULL, so we always have to
|
||
handle the unaligned case here. */
|
||
assemble_integer_with_op ("\t.4byte\t", value);
|
||
return true;
|
||
}
|
||
}
|
||
return default_assemble_integer (value, size, aligned_p);
|
||
}
|
||
|
||
/* Function to set up the backend function structure. */
|
||
|
||
static struct machine_function *
|
||
frv_init_machine_status ()
|
||
{
|
||
return ggc_alloc_cleared (sizeof (struct machine_function));
|
||
}
|
||
|
||
|
||
/* Update the register state information, to know about which registers are set
|
||
or clobbered. */
|
||
|
||
static void
|
||
frv_registers_update (x, reg_state, modified, p_num_mod, flag)
|
||
rtx x;
|
||
unsigned char reg_state[];
|
||
int modified[];
|
||
int *p_num_mod;
|
||
int flag;
|
||
{
|
||
int regno, reg_max;
|
||
rtx reg;
|
||
rtx cond;
|
||
const char *format;
|
||
int length;
|
||
int j;
|
||
|
||
switch (GET_CODE (x))
|
||
{
|
||
default:
|
||
break;
|
||
|
||
/* Clobber just modifies a register, it doesn't make it live. */
|
||
case CLOBBER:
|
||
frv_registers_update (XEXP (x, 0), reg_state, modified, p_num_mod,
|
||
flag | REGSTATE_MODIFIED);
|
||
return;
|
||
|
||
/* Pre modify updates the first argument, just references the second. */
|
||
case PRE_MODIFY:
|
||
case SET:
|
||
frv_registers_update (XEXP (x, 0), reg_state, modified, p_num_mod,
|
||
flag | REGSTATE_MODIFIED | REGSTATE_LIVE);
|
||
frv_registers_update (XEXP (x, 1), reg_state, modified, p_num_mod, flag);
|
||
return;
|
||
|
||
/* For COND_EXEC, pass the appropriate flag to evaluate the conditional
|
||
statement, but just to be sure, make sure it is the type of cond_exec
|
||
we expect. */
|
||
case COND_EXEC:
|
||
cond = XEXP (x, 0);
|
||
if ((GET_CODE (cond) == EQ || GET_CODE (cond) == NE)
|
||
&& GET_CODE (XEXP (cond, 0)) == REG
|
||
&& CR_P (REGNO (XEXP (cond, 0)))
|
||
&& GET_CODE (XEXP (cond, 1)) == CONST_INT
|
||
&& INTVAL (XEXP (cond, 1)) == 0
|
||
&& (flag & (REGSTATE_MODIFIED | REGSTATE_IF_EITHER)) == 0)
|
||
{
|
||
frv_registers_update (cond, reg_state, modified, p_num_mod, flag);
|
||
flag |= ((REGNO (XEXP (cond, 0)) - CR_FIRST)
|
||
| ((GET_CODE (cond) == NE)
|
||
? REGSTATE_IF_TRUE
|
||
: REGSTATE_IF_FALSE));
|
||
|
||
frv_registers_update (XEXP (x, 1), reg_state, modified, p_num_mod,
|
||
flag);
|
||
return;
|
||
}
|
||
else
|
||
fatal_insn ("frv_registers_update", x);
|
||
|
||
/* MEM resets the modification bits. */
|
||
case MEM:
|
||
flag &= ~REGSTATE_MODIFIED;
|
||
break;
|
||
|
||
/* See if we need to set the modified flag. */
|
||
case SUBREG:
|
||
reg = SUBREG_REG (x);
|
||
if (GET_CODE (reg) == REG)
|
||
{
|
||
regno = subreg_regno (x);
|
||
reg_max = REGNO (reg) + HARD_REGNO_NREGS (regno, GET_MODE (reg));
|
||
goto reg_common;
|
||
}
|
||
break;
|
||
|
||
case REG:
|
||
regno = REGNO (x);
|
||
reg_max = regno + HARD_REGNO_NREGS (regno, GET_MODE (x));
|
||
/* fall through */
|
||
|
||
reg_common:
|
||
if (flag & REGSTATE_MODIFIED)
|
||
{
|
||
flag &= REGSTATE_MASK;
|
||
while (regno < reg_max)
|
||
{
|
||
int rs = reg_state[regno];
|
||
|
||
if (flag != rs)
|
||
{
|
||
if ((rs & REGSTATE_MODIFIED) == 0)
|
||
{
|
||
modified[ *p_num_mod ] = regno;
|
||
(*p_num_mod)++;
|
||
}
|
||
|
||
/* If the previous register state had the register as
|
||
modified, possibly in some conditional execution context,
|
||
and the current insn modifies in some other context, or
|
||
outside of conditional execution, just mark the variable
|
||
as modified. */
|
||
else
|
||
flag &= ~(REGSTATE_IF_EITHER | REGSTATE_CC_MASK);
|
||
|
||
reg_state[regno] = (rs | flag);
|
||
}
|
||
regno++;
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
|
||
|
||
length = GET_RTX_LENGTH (GET_CODE (x));
|
||
format = GET_RTX_FORMAT (GET_CODE (x));
|
||
|
||
for (j = 0; j < length; ++j)
|
||
{
|
||
switch (format[j])
|
||
{
|
||
case 'e':
|
||
frv_registers_update (XEXP (x, j), reg_state, modified, p_num_mod,
|
||
flag);
|
||
break;
|
||
|
||
case 'V':
|
||
case 'E':
|
||
if (XVEC (x, j) != 0)
|
||
{
|
||
int k;
|
||
for (k = 0; k < XVECLEN (x, j); ++k)
|
||
frv_registers_update (XVECEXP (x, j, k), reg_state, modified,
|
||
p_num_mod, flag);
|
||
}
|
||
break;
|
||
|
||
default:
|
||
/* Nothing to do. */
|
||
break;
|
||
}
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
/* Return if any registers in a hard register set were used an insn. */
|
||
|
||
static int
|
||
frv_registers_used_p (x, reg_state, flag)
|
||
rtx x;
|
||
unsigned char reg_state[];
|
||
int flag;
|
||
{
|
||
int regno, reg_max;
|
||
rtx reg;
|
||
rtx cond;
|
||
rtx dest;
|
||
const char *format;
|
||
int result;
|
||
int length;
|
||
int j;
|
||
|
||
switch (GET_CODE (x))
|
||
{
|
||
default:
|
||
break;
|
||
|
||
/* Skip clobber, that doesn't use the previous value */
|
||
case CLOBBER:
|
||
return FALSE;
|
||
|
||
/* For SET, if a conditional jump has occurred in the same insn, only
|
||
allow a set of a CR register if that register is not currently live.
|
||
This is because on the FR-V, B0/B1 instructions are always last.
|
||
Otherwise, don't look at the result, except within a MEM, but do look
|
||
at the source. */
|
||
case SET:
|
||
dest = SET_DEST (x);
|
||
if (flag & REGSTATE_CONDJUMP
|
||
&& GET_CODE (dest) == REG && CR_P (REGNO (dest))
|
||
&& (reg_state[ REGNO (dest) ] & REGSTATE_LIVE) != 0)
|
||
return TRUE;
|
||
|
||
if (GET_CODE (dest) == MEM)
|
||
{
|
||
result = frv_registers_used_p (XEXP (dest, 0), reg_state, flag);
|
||
if (result)
|
||
return result;
|
||
}
|
||
|
||
return frv_registers_used_p (SET_SRC (x), reg_state, flag);
|
||
|
||
/* For COND_EXEC, pass the appropriate flag to evaluate the conditional
|
||
statement, but just to be sure, make sure it is the type of cond_exec
|
||
we expect. */
|
||
case COND_EXEC:
|
||
cond = XEXP (x, 0);
|
||
if ((GET_CODE (cond) == EQ || GET_CODE (cond) == NE)
|
||
&& GET_CODE (XEXP (cond, 0)) == REG
|
||
&& CR_P (REGNO (XEXP (cond, 0)))
|
||
&& GET_CODE (XEXP (cond, 1)) == CONST_INT
|
||
&& INTVAL (XEXP (cond, 1)) == 0
|
||
&& (flag & (REGSTATE_MODIFIED | REGSTATE_IF_EITHER)) == 0)
|
||
{
|
||
result = frv_registers_used_p (cond, reg_state, flag);
|
||
if (result)
|
||
return result;
|
||
|
||
flag |= ((REGNO (XEXP (cond, 0)) - CR_FIRST)
|
||
| ((GET_CODE (cond) == NE)
|
||
? REGSTATE_IF_TRUE
|
||
: REGSTATE_IF_FALSE));
|
||
|
||
return frv_registers_used_p (XEXP (x, 1), reg_state, flag);
|
||
}
|
||
else
|
||
fatal_insn ("frv_registers_used_p", x);
|
||
|
||
/* See if a register or subreg was modified in the same VLIW insn. */
|
||
case SUBREG:
|
||
reg = SUBREG_REG (x);
|
||
if (GET_CODE (reg) == REG)
|
||
{
|
||
regno = subreg_regno (x);
|
||
reg_max = REGNO (reg) + HARD_REGNO_NREGS (regno, GET_MODE (reg));
|
||
goto reg_common;
|
||
}
|
||
break;
|
||
|
||
case REG:
|
||
regno = REGNO (x);
|
||
reg_max = regno + HARD_REGNO_NREGS (regno, GET_MODE (x));
|
||
/* fall through */
|
||
|
||
reg_common:
|
||
while (regno < reg_max)
|
||
{
|
||
int rs = reg_state[regno];
|
||
|
||
if (rs & REGSTATE_MODIFIED)
|
||
{
|
||
int rs_if = rs & REGSTATE_IF_EITHER;
|
||
int flag_if = flag & REGSTATE_IF_EITHER;
|
||
|
||
/* Simple modification, no conditional execution */
|
||
if ((rs & REGSTATE_IF_EITHER) == 0)
|
||
return TRUE;
|
||
|
||
/* See if the variable is only modified in a conditional
|
||
execution expression opposite to the conditional execution
|
||
expression that governs this expression (ie, true vs. false
|
||
for the same CC register). If this isn't two halves of the
|
||
same conditional expression, consider the register
|
||
modified. */
|
||
if (((rs_if == REGSTATE_IF_TRUE && flag_if == REGSTATE_IF_FALSE)
|
||
|| (rs_if == REGSTATE_IF_FALSE && flag_if == REGSTATE_IF_TRUE))
|
||
&& ((rs & REGSTATE_CC_MASK) == (flag & REGSTATE_CC_MASK)))
|
||
;
|
||
else
|
||
return TRUE;
|
||
}
|
||
|
||
regno++;
|
||
}
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
length = GET_RTX_LENGTH (GET_CODE (x));
|
||
format = GET_RTX_FORMAT (GET_CODE (x));
|
||
|
||
for (j = 0; j < length; ++j)
|
||
{
|
||
switch (format[j])
|
||
{
|
||
case 'e':
|
||
result = frv_registers_used_p (XEXP (x, j), reg_state, flag);
|
||
if (result != 0)
|
||
return result;
|
||
break;
|
||
|
||
case 'V':
|
||
case 'E':
|
||
if (XVEC (x, j) != 0)
|
||
{
|
||
int k;
|
||
for (k = 0; k < XVECLEN (x, j); ++k)
|
||
{
|
||
result = frv_registers_used_p (XVECEXP (x, j, k), reg_state,
|
||
flag);
|
||
if (result != 0)
|
||
return result;
|
||
}
|
||
}
|
||
break;
|
||
|
||
default:
|
||
/* Nothing to do. */
|
||
break;
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* Return if any registers in a hard register set were set in an insn. */
|
||
|
||
static int
|
||
frv_registers_set_p (x, reg_state, modify_p)
|
||
rtx x;
|
||
unsigned char reg_state[];
|
||
int modify_p;
|
||
{
|
||
int regno, reg_max;
|
||
rtx reg;
|
||
rtx cond;
|
||
const char *format;
|
||
int length;
|
||
int j;
|
||
|
||
switch (GET_CODE (x))
|
||
{
|
||
default:
|
||
break;
|
||
|
||
case CLOBBER:
|
||
return frv_registers_set_p (XEXP (x, 0), reg_state, TRUE);
|
||
|
||
case PRE_MODIFY:
|
||
case SET:
|
||
return (frv_registers_set_p (XEXP (x, 0), reg_state, TRUE)
|
||
|| frv_registers_set_p (XEXP (x, 1), reg_state, FALSE));
|
||
|
||
case COND_EXEC:
|
||
cond = XEXP (x, 0);
|
||
/* just to be sure, make sure it is the type of cond_exec we
|
||
expect. */
|
||
if ((GET_CODE (cond) == EQ || GET_CODE (cond) == NE)
|
||
&& GET_CODE (XEXP (cond, 0)) == REG
|
||
&& CR_P (REGNO (XEXP (cond, 0)))
|
||
&& GET_CODE (XEXP (cond, 1)) == CONST_INT
|
||
&& INTVAL (XEXP (cond, 1)) == 0
|
||
&& !modify_p)
|
||
return frv_registers_set_p (XEXP (x, 1), reg_state, modify_p);
|
||
else
|
||
fatal_insn ("frv_registers_set_p", x);
|
||
|
||
/* MEM resets the modification bits. */
|
||
case MEM:
|
||
modify_p = FALSE;
|
||
break;
|
||
|
||
/* See if we need to set the modified modify_p. */
|
||
case SUBREG:
|
||
reg = SUBREG_REG (x);
|
||
if (GET_CODE (reg) == REG)
|
||
{
|
||
regno = subreg_regno (x);
|
||
reg_max = REGNO (reg) + HARD_REGNO_NREGS (regno, GET_MODE (reg));
|
||
goto reg_common;
|
||
}
|
||
break;
|
||
|
||
case REG:
|
||
regno = REGNO (x);
|
||
reg_max = regno + HARD_REGNO_NREGS (regno, GET_MODE (x));
|
||
/* fall through */
|
||
|
||
reg_common:
|
||
if (modify_p)
|
||
while (regno < reg_max)
|
||
{
|
||
int rs = reg_state[regno];
|
||
|
||
if (rs & REGSTATE_MODIFIED)
|
||
return TRUE;
|
||
regno++;
|
||
}
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
length = GET_RTX_LENGTH (GET_CODE (x));
|
||
format = GET_RTX_FORMAT (GET_CODE (x));
|
||
|
||
for (j = 0; j < length; ++j)
|
||
{
|
||
switch (format[j])
|
||
{
|
||
case 'e':
|
||
if (frv_registers_set_p (XEXP (x, j), reg_state, modify_p))
|
||
return TRUE;
|
||
break;
|
||
|
||
case 'V':
|
||
case 'E':
|
||
if (XVEC (x, j) != 0)
|
||
{
|
||
int k;
|
||
for (k = 0; k < XVECLEN (x, j); ++k)
|
||
if (frv_registers_set_p (XVECEXP (x, j, k), reg_state,
|
||
modify_p))
|
||
return TRUE;
|
||
}
|
||
break;
|
||
|
||
default:
|
||
/* Nothing to do. */
|
||
break;
|
||
}
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
/* In rare cases, correct code generation requires extra machine dependent
|
||
processing between the second jump optimization pass and delayed branch
|
||
scheduling. On those machines, define this macro as a C statement to act on
|
||
the code starting at INSN. */
|
||
|
||
/* On the FR-V, this pass is used to rescan the insn chain, and pack
|
||
conditional branches/calls/jumps, etc. with previous insns where it can. It
|
||
does not reorder the instructions. We assume the scheduler left the flow
|
||
information in a reasonable state. */
|
||
|
||
static void
|
||
frv_pack_insns ()
|
||
{
|
||
state_t frv_state; /* frv state machine */
|
||
int cur_start_vliw_p; /* current insn starts a VLIW insn */
|
||
int next_start_vliw_p; /* next insn starts a VLIW insn */
|
||
int cur_condjump_p; /* flag if current insn is a cond jump*/
|
||
int next_condjump_p; /* flag if next insn is a cond jump */
|
||
rtx insn;
|
||
rtx link;
|
||
int j;
|
||
int num_mod = 0; /* # of modified registers */
|
||
int modified[FIRST_PSEUDO_REGISTER]; /* registers modified in current VLIW */
|
||
/* register state information */
|
||
unsigned char reg_state[FIRST_PSEUDO_REGISTER];
|
||
|
||
/* If we weren't going to pack the insns, don't bother with this pass. */
|
||
if (!optimize || !flag_schedule_insns_after_reload || TARGET_NO_VLIW_BRANCH)
|
||
return;
|
||
|
||
switch (frv_cpu_type)
|
||
{
|
||
default:
|
||
case FRV_CPU_FR300: /* FR300/simple are single issue */
|
||
case FRV_CPU_SIMPLE:
|
||
return;
|
||
|
||
case FRV_CPU_GENERIC: /* FR-V and FR500 are multi-issue */
|
||
case FRV_CPU_FR400:
|
||
case FRV_CPU_FR500:
|
||
case FRV_CPU_TOMCAT:
|
||
break;
|
||
}
|
||
|
||
/* Set up the instruction and register states. */
|
||
dfa_start ();
|
||
frv_state = (state_t) xmalloc (state_size ());
|
||
memset ((PTR) reg_state, REGSTATE_DEAD, sizeof (reg_state));
|
||
|
||
/* Go through the insns, and repack the insns. */
|
||
state_reset (frv_state);
|
||
cur_start_vliw_p = FALSE;
|
||
next_start_vliw_p = TRUE;
|
||
cur_condjump_p = 0;
|
||
next_condjump_p = 0;
|
||
|
||
for (insn = get_insns (); insn != NULL_RTX; insn = NEXT_INSN (insn))
|
||
{
|
||
enum rtx_code code = GET_CODE (insn);
|
||
enum rtx_code pattern_code;
|
||
|
||
/* For basic block begin notes redo the live information, and skip other
|
||
notes. */
|
||
if (code == NOTE)
|
||
{
|
||
if (NOTE_LINE_NUMBER (insn) == (int)NOTE_INSN_BASIC_BLOCK)
|
||
{
|
||
regset live;
|
||
|
||
for (j = 0; j < FIRST_PSEUDO_REGISTER; j++)
|
||
reg_state[j] &= ~ REGSTATE_LIVE;
|
||
|
||
live = NOTE_BASIC_BLOCK (insn)->global_live_at_start;
|
||
EXECUTE_IF_SET_IN_REG_SET(live, 0, j,
|
||
{
|
||
reg_state[j] |= REGSTATE_LIVE;
|
||
});
|
||
}
|
||
|
||
continue;
|
||
}
|
||
|
||
/* things like labels reset everything. */
|
||
if (GET_RTX_CLASS (code) != 'i')
|
||
{
|
||
next_start_vliw_p = TRUE;
|
||
continue;
|
||
}
|
||
|
||
/* Clear the VLIW start flag on random USE and CLOBBER insns, which is
|
||
set on the USE insn that preceeds the return, and potentially on
|
||
CLOBBERs for setting multiword variables. Also skip the ADDR_VEC
|
||
holding the case table labels. */
|
||
pattern_code = GET_CODE (PATTERN (insn));
|
||
if (pattern_code == USE || pattern_code == CLOBBER
|
||
|| pattern_code == ADDR_VEC || pattern_code == ADDR_DIFF_VEC)
|
||
{
|
||
CLEAR_VLIW_START (insn);
|
||
continue;
|
||
}
|
||
|
||
cur_start_vliw_p = next_start_vliw_p;
|
||
next_start_vliw_p = FALSE;
|
||
|
||
cur_condjump_p |= next_condjump_p;
|
||
next_condjump_p = 0;
|
||
|
||
/* Unconditional branches and calls end the current VLIW insn. */
|
||
if (code == CALL_INSN)
|
||
{
|
||
next_start_vliw_p = TRUE;
|
||
|
||
/* On a TOMCAT, calls must be alone in the VLIW insns. */
|
||
if (frv_cpu_type == FRV_CPU_TOMCAT)
|
||
cur_start_vliw_p = TRUE;
|
||
}
|
||
else if (code == JUMP_INSN)
|
||
{
|
||
if (any_condjump_p (insn))
|
||
next_condjump_p = REGSTATE_CONDJUMP;
|
||
else
|
||
next_start_vliw_p = TRUE;
|
||
}
|
||
|
||
/* Only allow setting a CCR register after a conditional branch. */
|
||
else if (((cur_condjump_p & REGSTATE_CONDJUMP) != 0)
|
||
&& get_attr_type (insn) != TYPE_CCR)
|
||
cur_start_vliw_p = TRUE;
|
||
|
||
/* Determine if we need to start a new VLIW instruction. */
|
||
if (cur_start_vliw_p
|
||
/* Do not check for register conflicts in a setlo instruction
|
||
because any output or true dependencies will be with the
|
||
partnering sethi instruction, with which it can be packed.
|
||
|
||
Although output dependencies are rare they are still
|
||
possible. So check output dependencies in VLIW insn. */
|
||
|| (get_attr_type (insn) != TYPE_SETLO
|
||
&& (frv_registers_used_p (PATTERN (insn),
|
||
reg_state,
|
||
cur_condjump_p)
|
||
|| frv_registers_set_p (PATTERN (insn), reg_state, FALSE)))
|
||
|| state_transition (frv_state, insn) >= 0)
|
||
{
|
||
SET_VLIW_START (insn);
|
||
state_reset (frv_state);
|
||
state_transition (frv_state, insn);
|
||
cur_condjump_p = 0;
|
||
|
||
/* Update the modified registers. */
|
||
for (j = 0; j < num_mod; j++)
|
||
reg_state[ modified[j] ] &= ~(REGSTATE_CC_MASK
|
||
| REGSTATE_IF_EITHER
|
||
| REGSTATE_MODIFIED);
|
||
|
||
num_mod = 0;
|
||
}
|
||
else
|
||
CLEAR_VLIW_START (insn);
|
||
|
||
/* Record which registers are modified. */
|
||
frv_registers_update (PATTERN (insn), reg_state, modified, &num_mod, 0);
|
||
|
||
/* Process the death notices */
|
||
for (link = REG_NOTES (insn);
|
||
link != NULL_RTX;
|
||
link = XEXP (link, 1))
|
||
{
|
||
rtx reg = XEXP (link, 0);
|
||
|
||
if (REG_NOTE_KIND (link) == REG_DEAD && GET_CODE (reg) == REG)
|
||
{
|
||
int regno = REGNO (reg);
|
||
int n = regno + HARD_REGNO_NREGS (regno, GET_MODE (reg));
|
||
for (; regno < n; regno++)
|
||
reg_state[regno] &= ~REGSTATE_LIVE;
|
||
}
|
||
}
|
||
}
|
||
|
||
free ((PTR) frv_state);
|
||
dfa_finish ();
|
||
return;
|
||
}
|
||
|
||
|
||
#define def_builtin(name, type, code) \
|
||
builtin_function ((name), (type), (code), BUILT_IN_MD, NULL, NULL)
|
||
|
||
struct builtin_description
|
||
{
|
||
enum insn_code icode;
|
||
const char *name;
|
||
enum frv_builtins code;
|
||
enum rtx_code comparison;
|
||
unsigned int flag;
|
||
};
|
||
|
||
/* Media intrinsics that take a single, constant argument. */
|
||
|
||
static struct builtin_description bdesc_set[] =
|
||
{
|
||
{ CODE_FOR_mhdsets, "__MHDSETS", FRV_BUILTIN_MHDSETS, 0, 0 }
|
||
};
|
||
|
||
/* Media intrinsics that take just one argument. */
|
||
|
||
static struct builtin_description bdesc_1arg[] =
|
||
{
|
||
{ CODE_FOR_mnot, "__MNOT", FRV_BUILTIN_MNOT, 0, 0 },
|
||
{ CODE_FOR_munpackh, "__MUNPACKH", FRV_BUILTIN_MUNPACKH, 0, 0 },
|
||
{ CODE_FOR_mbtoh, "__MBTOH", FRV_BUILTIN_MBTOH, 0, 0 },
|
||
{ CODE_FOR_mhtob, "__MHTOB", FRV_BUILTIN_MHTOB, 0, 0 },
|
||
{ CODE_FOR_mabshs, "__MABSHS", FRV_BUILTIN_MABSHS, 0, 0 }
|
||
};
|
||
|
||
/* Media intrinsics that take two arguments. */
|
||
|
||
static struct builtin_description bdesc_2arg[] =
|
||
{
|
||
{ CODE_FOR_mand, "__MAND", FRV_BUILTIN_MAND, 0, 0 },
|
||
{ CODE_FOR_mor, "__MOR", FRV_BUILTIN_MOR, 0, 0 },
|
||
{ CODE_FOR_mxor, "__MXOR", FRV_BUILTIN_MXOR, 0, 0 },
|
||
{ CODE_FOR_maveh, "__MAVEH", FRV_BUILTIN_MAVEH, 0, 0 },
|
||
{ CODE_FOR_msaths, "__MSATHS", FRV_BUILTIN_MSATHS, 0, 0 },
|
||
{ CODE_FOR_msathu, "__MSATHU", FRV_BUILTIN_MSATHU, 0, 0 },
|
||
{ CODE_FOR_maddhss, "__MADDHSS", FRV_BUILTIN_MADDHSS, 0, 0 },
|
||
{ CODE_FOR_maddhus, "__MADDHUS", FRV_BUILTIN_MADDHUS, 0, 0 },
|
||
{ CODE_FOR_msubhss, "__MSUBHSS", FRV_BUILTIN_MSUBHSS, 0, 0 },
|
||
{ CODE_FOR_msubhus, "__MSUBHUS", FRV_BUILTIN_MSUBHUS, 0, 0 },
|
||
{ CODE_FOR_mqaddhss, "__MQADDHSS", FRV_BUILTIN_MQADDHSS, 0, 0 },
|
||
{ CODE_FOR_mqaddhus, "__MQADDHUS", FRV_BUILTIN_MQADDHUS, 0, 0 },
|
||
{ CODE_FOR_mqsubhss, "__MQSUBHSS", FRV_BUILTIN_MQSUBHSS, 0, 0 },
|
||
{ CODE_FOR_mqsubhus, "__MQSUBHUS", FRV_BUILTIN_MQSUBHUS, 0, 0 },
|
||
{ CODE_FOR_mpackh, "__MPACKH", FRV_BUILTIN_MPACKH, 0, 0 },
|
||
{ CODE_FOR_mdpackh, "__MDPACKH", FRV_BUILTIN_MDPACKH, 0, 0 },
|
||
{ CODE_FOR_mcop1, "__Mcop1", FRV_BUILTIN_MCOP1, 0, 0 },
|
||
{ CODE_FOR_mcop2, "__Mcop2", FRV_BUILTIN_MCOP2, 0, 0 },
|
||
{ CODE_FOR_mwcut, "__MWCUT", FRV_BUILTIN_MWCUT, 0, 0 },
|
||
{ CODE_FOR_mqsaths, "__MQSATHS", FRV_BUILTIN_MQSATHS, 0, 0 }
|
||
};
|
||
|
||
/* Media intrinsics that take two arguments, the first being an ACC number. */
|
||
|
||
static struct builtin_description bdesc_cut[] =
|
||
{
|
||
{ CODE_FOR_mcut, "__MCUT", FRV_BUILTIN_MCUT, 0, 0 },
|
||
{ CODE_FOR_mcutss, "__MCUTSS", FRV_BUILTIN_MCUTSS, 0, 0 },
|
||
{ CODE_FOR_mdcutssi, "__MDCUTSSI", FRV_BUILTIN_MDCUTSSI, 0, 0 }
|
||
};
|
||
|
||
/* Two-argument media intrinsics with an immediate second argument. */
|
||
|
||
static struct builtin_description bdesc_2argimm[] =
|
||
{
|
||
{ CODE_FOR_mrotli, "__MROTLI", FRV_BUILTIN_MROTLI, 0, 0 },
|
||
{ CODE_FOR_mrotri, "__MROTRI", FRV_BUILTIN_MROTRI, 0, 0 },
|
||
{ CODE_FOR_msllhi, "__MSLLHI", FRV_BUILTIN_MSLLHI, 0, 0 },
|
||
{ CODE_FOR_msrlhi, "__MSRLHI", FRV_BUILTIN_MSRLHI, 0, 0 },
|
||
{ CODE_FOR_msrahi, "__MSRAHI", FRV_BUILTIN_MSRAHI, 0, 0 },
|
||
{ CODE_FOR_mexpdhw, "__MEXPDHW", FRV_BUILTIN_MEXPDHW, 0, 0 },
|
||
{ CODE_FOR_mexpdhd, "__MEXPDHD", FRV_BUILTIN_MEXPDHD, 0, 0 },
|
||
{ CODE_FOR_mdrotli, "__MDROTLI", FRV_BUILTIN_MDROTLI, 0, 0 },
|
||
{ CODE_FOR_mcplhi, "__MCPLHI", FRV_BUILTIN_MCPLHI, 0, 0 },
|
||
{ CODE_FOR_mcpli, "__MCPLI", FRV_BUILTIN_MCPLI, 0, 0 },
|
||
{ CODE_FOR_mhsetlos, "__MHSETLOS", FRV_BUILTIN_MHSETLOS, 0, 0 },
|
||
{ CODE_FOR_mhsetloh, "__MHSETLOH", FRV_BUILTIN_MHSETLOH, 0, 0 },
|
||
{ CODE_FOR_mhsethis, "__MHSETHIS", FRV_BUILTIN_MHSETHIS, 0, 0 },
|
||
{ CODE_FOR_mhsethih, "__MHSETHIH", FRV_BUILTIN_MHSETHIH, 0, 0 },
|
||
{ CODE_FOR_mhdseth, "__MHDSETH", FRV_BUILTIN_MHDSETH, 0, 0 }
|
||
};
|
||
|
||
/* Media intrinsics that take two arguments and return void, the first argument
|
||
being a pointer to 4 words in memory. */
|
||
|
||
static struct builtin_description bdesc_void2arg[] =
|
||
{
|
||
{ CODE_FOR_mdunpackh, "__MDUNPACKH", FRV_BUILTIN_MDUNPACKH, 0, 0 },
|
||
{ CODE_FOR_mbtohe, "__MBTOHE", FRV_BUILTIN_MBTOHE, 0, 0 },
|
||
};
|
||
|
||
/* Media intrinsics that take three arguments, the first being a const_int that
|
||
denotes an accumulator, and that return void. */
|
||
|
||
static struct builtin_description bdesc_void3arg[] =
|
||
{
|
||
{ CODE_FOR_mcpxrs, "__MCPXRS", FRV_BUILTIN_MCPXRS, 0, 0 },
|
||
{ CODE_FOR_mcpxru, "__MCPXRU", FRV_BUILTIN_MCPXRU, 0, 0 },
|
||
{ CODE_FOR_mcpxis, "__MCPXIS", FRV_BUILTIN_MCPXIS, 0, 0 },
|
||
{ CODE_FOR_mcpxiu, "__MCPXIU", FRV_BUILTIN_MCPXIU, 0, 0 },
|
||
{ CODE_FOR_mmulhs, "__MMULHS", FRV_BUILTIN_MMULHS, 0, 0 },
|
||
{ CODE_FOR_mmulhu, "__MMULHU", FRV_BUILTIN_MMULHU, 0, 0 },
|
||
{ CODE_FOR_mmulxhs, "__MMULXHS", FRV_BUILTIN_MMULXHS, 0, 0 },
|
||
{ CODE_FOR_mmulxhu, "__MMULXHU", FRV_BUILTIN_MMULXHU, 0, 0 },
|
||
{ CODE_FOR_mmachs, "__MMACHS", FRV_BUILTIN_MMACHS, 0, 0 },
|
||
{ CODE_FOR_mmachu, "__MMACHU", FRV_BUILTIN_MMACHU, 0, 0 },
|
||
{ CODE_FOR_mmrdhs, "__MMRDHS", FRV_BUILTIN_MMRDHS, 0, 0 },
|
||
{ CODE_FOR_mmrdhu, "__MMRDHU", FRV_BUILTIN_MMRDHU, 0, 0 },
|
||
{ CODE_FOR_mqcpxrs, "__MQCPXRS", FRV_BUILTIN_MQCPXRS, 0, 0 },
|
||
{ CODE_FOR_mqcpxru, "__MQCPXRU", FRV_BUILTIN_MQCPXRU, 0, 0 },
|
||
{ CODE_FOR_mqcpxis, "__MQCPXIS", FRV_BUILTIN_MQCPXIS, 0, 0 },
|
||
{ CODE_FOR_mqcpxiu, "__MQCPXIU", FRV_BUILTIN_MQCPXIU, 0, 0 },
|
||
{ CODE_FOR_mqmulhs, "__MQMULHS", FRV_BUILTIN_MQMULHS, 0, 0 },
|
||
{ CODE_FOR_mqmulhu, "__MQMULHU", FRV_BUILTIN_MQMULHU, 0, 0 },
|
||
{ CODE_FOR_mqmulxhs, "__MQMULXHS", FRV_BUILTIN_MQMULXHS, 0, 0 },
|
||
{ CODE_FOR_mqmulxhu, "__MQMULXHU", FRV_BUILTIN_MQMULXHU, 0, 0 },
|
||
{ CODE_FOR_mqmachs, "__MQMACHS", FRV_BUILTIN_MQMACHS, 0, 0 },
|
||
{ CODE_FOR_mqmachu, "__MQMACHU", FRV_BUILTIN_MQMACHU, 0, 0 },
|
||
{ CODE_FOR_mqxmachs, "__MQXMACHS", FRV_BUILTIN_MQXMACHS, 0, 0 },
|
||
{ CODE_FOR_mqxmacxhs, "__MQXMACXHS", FRV_BUILTIN_MQXMACXHS, 0, 0 },
|
||
{ CODE_FOR_mqmacxhs, "__MQMACXHS", FRV_BUILTIN_MQMACXHS, 0, 0 }
|
||
};
|
||
|
||
/* Media intrinsics that take two accumulator numbers as argument and
|
||
return void. */
|
||
|
||
static struct builtin_description bdesc_voidacc[] =
|
||
{
|
||
{ CODE_FOR_maddaccs, "__MADDACCS", FRV_BUILTIN_MADDACCS, 0, 0 },
|
||
{ CODE_FOR_msubaccs, "__MSUBACCS", FRV_BUILTIN_MSUBACCS, 0, 0 },
|
||
{ CODE_FOR_masaccs, "__MASACCS", FRV_BUILTIN_MASACCS, 0, 0 },
|
||
{ CODE_FOR_mdaddaccs, "__MDADDACCS", FRV_BUILTIN_MDADDACCS, 0, 0 },
|
||
{ CODE_FOR_mdsubaccs, "__MDSUBACCS", FRV_BUILTIN_MDSUBACCS, 0, 0 },
|
||
{ CODE_FOR_mdasaccs, "__MDASACCS", FRV_BUILTIN_MDASACCS, 0, 0 }
|
||
};
|
||
|
||
/* Initialize media builtins. */
|
||
|
||
static void
|
||
frv_init_builtins ()
|
||
{
|
||
tree endlink = void_list_node;
|
||
tree accumulator = integer_type_node;
|
||
tree integer = integer_type_node;
|
||
tree voidt = void_type_node;
|
||
tree uhalf = short_unsigned_type_node;
|
||
tree sword1 = long_integer_type_node;
|
||
tree uword1 = long_unsigned_type_node;
|
||
tree sword2 = long_long_integer_type_node;
|
||
tree uword2 = long_long_unsigned_type_node;
|
||
tree uword4 = build_pointer_type (uword1);
|
||
|
||
#define UNARY(RET, T1) \
|
||
build_function_type (RET, tree_cons (NULL_TREE, T1, endlink))
|
||
|
||
#define BINARY(RET, T1, T2) \
|
||
build_function_type (RET, tree_cons (NULL_TREE, T1, \
|
||
tree_cons (NULL_TREE, T2, endlink)))
|
||
|
||
#define TRINARY(RET, T1, T2, T3) \
|
||
build_function_type (RET, tree_cons (NULL_TREE, T1, \
|
||
tree_cons (NULL_TREE, T2, \
|
||
tree_cons (NULL_TREE, T3, endlink))))
|
||
|
||
tree void_ftype_void = build_function_type (voidt, endlink);
|
||
|
||
tree void_ftype_acc = UNARY (voidt, accumulator);
|
||
tree void_ftype_uw4_uw1 = BINARY (voidt, uword4, uword1);
|
||
tree void_ftype_uw4_uw2 = BINARY (voidt, uword4, uword2);
|
||
tree void_ftype_acc_uw1 = BINARY (voidt, accumulator, uword1);
|
||
tree void_ftype_acc_acc = BINARY (voidt, accumulator, accumulator);
|
||
tree void_ftype_acc_uw1_uw1 = TRINARY (voidt, accumulator, uword1, uword1);
|
||
tree void_ftype_acc_sw1_sw1 = TRINARY (voidt, accumulator, sword1, sword1);
|
||
tree void_ftype_acc_uw2_uw2 = TRINARY (voidt, accumulator, uword2, uword2);
|
||
tree void_ftype_acc_sw2_sw2 = TRINARY (voidt, accumulator, sword2, sword2);
|
||
|
||
tree uw1_ftype_uw1 = UNARY (uword1, uword1);
|
||
tree uw1_ftype_sw1 = UNARY (uword1, sword1);
|
||
tree uw1_ftype_uw2 = UNARY (uword1, uword2);
|
||
tree uw1_ftype_acc = UNARY (uword1, accumulator);
|
||
tree uw1_ftype_uh_uh = BINARY (uword1, uhalf, uhalf);
|
||
tree uw1_ftype_uw1_uw1 = BINARY (uword1, uword1, uword1);
|
||
tree uw1_ftype_uw1_int = BINARY (uword1, uword1, integer);
|
||
tree uw1_ftype_acc_uw1 = BINARY (uword1, accumulator, uword1);
|
||
tree uw1_ftype_acc_sw1 = BINARY (uword1, accumulator, sword1);
|
||
tree uw1_ftype_uw2_uw1 = BINARY (uword1, uword2, uword1);
|
||
tree uw1_ftype_uw2_int = BINARY (uword1, uword2, integer);
|
||
|
||
tree sw1_ftype_int = UNARY (sword1, integer);
|
||
tree sw1_ftype_sw1_sw1 = BINARY (sword1, sword1, sword1);
|
||
tree sw1_ftype_sw1_int = BINARY (sword1, sword1, integer);
|
||
|
||
tree uw2_ftype_uw1 = UNARY (uword2, uword1);
|
||
tree uw2_ftype_uw1_int = BINARY (uword2, uword1, integer);
|
||
tree uw2_ftype_uw2_uw2 = BINARY (uword2, uword2, uword2);
|
||
tree uw2_ftype_uw2_int = BINARY (uword2, uword2, integer);
|
||
tree uw2_ftype_acc_int = BINARY (uword2, accumulator, integer);
|
||
|
||
tree sw2_ftype_sw2_sw2 = BINARY (sword2, sword2, sword2);
|
||
|
||
def_builtin ("__MAND", uw1_ftype_uw1_uw1, FRV_BUILTIN_MAND);
|
||
def_builtin ("__MOR", uw1_ftype_uw1_uw1, FRV_BUILTIN_MOR);
|
||
def_builtin ("__MXOR", uw1_ftype_uw1_uw1, FRV_BUILTIN_MXOR);
|
||
def_builtin ("__MNOT", uw1_ftype_uw1, FRV_BUILTIN_MNOT);
|
||
def_builtin ("__MROTLI", uw1_ftype_uw1_int, FRV_BUILTIN_MROTLI);
|
||
def_builtin ("__MROTRI", uw1_ftype_uw1_int, FRV_BUILTIN_MROTRI);
|
||
def_builtin ("__MWCUT", uw1_ftype_uw2_uw1, FRV_BUILTIN_MWCUT);
|
||
def_builtin ("__MAVEH", uw1_ftype_uw1_uw1, FRV_BUILTIN_MAVEH);
|
||
def_builtin ("__MSLLHI", uw1_ftype_uw1_int, FRV_BUILTIN_MSLLHI);
|
||
def_builtin ("__MSRLHI", uw1_ftype_uw1_int, FRV_BUILTIN_MSRLHI);
|
||
def_builtin ("__MSRAHI", sw1_ftype_sw1_int, FRV_BUILTIN_MSRAHI);
|
||
def_builtin ("__MSATHS", sw1_ftype_sw1_sw1, FRV_BUILTIN_MSATHS);
|
||
def_builtin ("__MSATHU", uw1_ftype_uw1_uw1, FRV_BUILTIN_MSATHU);
|
||
def_builtin ("__MADDHSS", sw1_ftype_sw1_sw1, FRV_BUILTIN_MADDHSS);
|
||
def_builtin ("__MADDHUS", uw1_ftype_uw1_uw1, FRV_BUILTIN_MADDHUS);
|
||
def_builtin ("__MSUBHSS", sw1_ftype_sw1_sw1, FRV_BUILTIN_MSUBHSS);
|
||
def_builtin ("__MSUBHUS", uw1_ftype_uw1_uw1, FRV_BUILTIN_MSUBHUS);
|
||
def_builtin ("__MMULHS", void_ftype_acc_sw1_sw1, FRV_BUILTIN_MMULHS);
|
||
def_builtin ("__MMULHU", void_ftype_acc_uw1_uw1, FRV_BUILTIN_MMULHU);
|
||
def_builtin ("__MMULXHS", void_ftype_acc_sw1_sw1, FRV_BUILTIN_MMULXHS);
|
||
def_builtin ("__MMULXHU", void_ftype_acc_uw1_uw1, FRV_BUILTIN_MMULXHU);
|
||
def_builtin ("__MMACHS", void_ftype_acc_sw1_sw1, FRV_BUILTIN_MMACHS);
|
||
def_builtin ("__MMACHU", void_ftype_acc_uw1_uw1, FRV_BUILTIN_MMACHU);
|
||
def_builtin ("__MMRDHS", void_ftype_acc_sw1_sw1, FRV_BUILTIN_MMRDHS);
|
||
def_builtin ("__MMRDHU", void_ftype_acc_uw1_uw1, FRV_BUILTIN_MMRDHU);
|
||
def_builtin ("__MQADDHSS", sw2_ftype_sw2_sw2, FRV_BUILTIN_MQADDHSS);
|
||
def_builtin ("__MQADDHUS", uw2_ftype_uw2_uw2, FRV_BUILTIN_MQADDHUS);
|
||
def_builtin ("__MQSUBHSS", sw2_ftype_sw2_sw2, FRV_BUILTIN_MQSUBHSS);
|
||
def_builtin ("__MQSUBHUS", uw2_ftype_uw2_uw2, FRV_BUILTIN_MQSUBHUS);
|
||
def_builtin ("__MQMULHS", void_ftype_acc_sw2_sw2, FRV_BUILTIN_MQMULHS);
|
||
def_builtin ("__MQMULHU", void_ftype_acc_uw2_uw2, FRV_BUILTIN_MQMULHU);
|
||
def_builtin ("__MQMULXHS", void_ftype_acc_sw2_sw2, FRV_BUILTIN_MQMULXHS);
|
||
def_builtin ("__MQMULXHU", void_ftype_acc_uw2_uw2, FRV_BUILTIN_MQMULXHU);
|
||
def_builtin ("__MQMACHS", void_ftype_acc_sw2_sw2, FRV_BUILTIN_MQMACHS);
|
||
def_builtin ("__MQMACHU", void_ftype_acc_uw2_uw2, FRV_BUILTIN_MQMACHU);
|
||
def_builtin ("__MCPXRS", void_ftype_acc_sw1_sw1, FRV_BUILTIN_MCPXRS);
|
||
def_builtin ("__MCPXRU", void_ftype_acc_uw1_uw1, FRV_BUILTIN_MCPXRU);
|
||
def_builtin ("__MCPXIS", void_ftype_acc_sw1_sw1, FRV_BUILTIN_MCPXIS);
|
||
def_builtin ("__MCPXIU", void_ftype_acc_uw1_uw1, FRV_BUILTIN_MCPXIU);
|
||
def_builtin ("__MQCPXRS", void_ftype_acc_sw2_sw2, FRV_BUILTIN_MQCPXRS);
|
||
def_builtin ("__MQCPXRU", void_ftype_acc_uw2_uw2, FRV_BUILTIN_MQCPXRU);
|
||
def_builtin ("__MQCPXIS", void_ftype_acc_sw2_sw2, FRV_BUILTIN_MQCPXIS);
|
||
def_builtin ("__MQCPXIU", void_ftype_acc_uw2_uw2, FRV_BUILTIN_MQCPXIU);
|
||
def_builtin ("__MCUT", uw1_ftype_acc_uw1, FRV_BUILTIN_MCUT);
|
||
def_builtin ("__MCUTSS", uw1_ftype_acc_sw1, FRV_BUILTIN_MCUTSS);
|
||
def_builtin ("__MEXPDHW", uw1_ftype_uw1_int, FRV_BUILTIN_MEXPDHW);
|
||
def_builtin ("__MEXPDHD", uw2_ftype_uw1_int, FRV_BUILTIN_MEXPDHD);
|
||
def_builtin ("__MPACKH", uw1_ftype_uh_uh, FRV_BUILTIN_MPACKH);
|
||
def_builtin ("__MUNPACKH", uw2_ftype_uw1, FRV_BUILTIN_MUNPACKH);
|
||
def_builtin ("__MDPACKH", uw2_ftype_uw2_uw2, FRV_BUILTIN_MDPACKH);
|
||
def_builtin ("__MDUNPACKH", void_ftype_uw4_uw2, FRV_BUILTIN_MDUNPACKH);
|
||
def_builtin ("__MBTOH", uw2_ftype_uw1, FRV_BUILTIN_MBTOH);
|
||
def_builtin ("__MHTOB", uw1_ftype_uw2, FRV_BUILTIN_MHTOB);
|
||
def_builtin ("__MBTOHE", void_ftype_uw4_uw1, FRV_BUILTIN_MBTOHE);
|
||
def_builtin ("__MCLRACC", void_ftype_acc, FRV_BUILTIN_MCLRACC);
|
||
def_builtin ("__MCLRACCA", void_ftype_void, FRV_BUILTIN_MCLRACCA);
|
||
def_builtin ("__MRDACC", uw1_ftype_acc, FRV_BUILTIN_MRDACC);
|
||
def_builtin ("__MRDACCG", uw1_ftype_acc, FRV_BUILTIN_MRDACCG);
|
||
def_builtin ("__MWTACC", void_ftype_acc_uw1, FRV_BUILTIN_MWTACC);
|
||
def_builtin ("__MWTACCG", void_ftype_acc_uw1, FRV_BUILTIN_MWTACCG);
|
||
def_builtin ("__Mcop1", uw1_ftype_uw1_uw1, FRV_BUILTIN_MCOP1);
|
||
def_builtin ("__Mcop2", uw1_ftype_uw1_uw1, FRV_BUILTIN_MCOP2);
|
||
def_builtin ("__MTRAP", void_ftype_void, FRV_BUILTIN_MTRAP);
|
||
def_builtin ("__MQXMACHS", void_ftype_acc_sw2_sw2, FRV_BUILTIN_MQXMACHS);
|
||
def_builtin ("__MQXMACXHS", void_ftype_acc_sw2_sw2, FRV_BUILTIN_MQXMACXHS);
|
||
def_builtin ("__MQMACXHS", void_ftype_acc_sw2_sw2, FRV_BUILTIN_MQMACXHS);
|
||
def_builtin ("__MADDACCS", void_ftype_acc_acc, FRV_BUILTIN_MADDACCS);
|
||
def_builtin ("__MSUBACCS", void_ftype_acc_acc, FRV_BUILTIN_MSUBACCS);
|
||
def_builtin ("__MASACCS", void_ftype_acc_acc, FRV_BUILTIN_MASACCS);
|
||
def_builtin ("__MDADDACCS", void_ftype_acc_acc, FRV_BUILTIN_MDADDACCS);
|
||
def_builtin ("__MDSUBACCS", void_ftype_acc_acc, FRV_BUILTIN_MDSUBACCS);
|
||
def_builtin ("__MDASACCS", void_ftype_acc_acc, FRV_BUILTIN_MDASACCS);
|
||
def_builtin ("__MABSHS", uw1_ftype_sw1, FRV_BUILTIN_MABSHS);
|
||
def_builtin ("__MDROTLI", uw2_ftype_uw2_int, FRV_BUILTIN_MDROTLI);
|
||
def_builtin ("__MCPLHI", uw1_ftype_uw2_int, FRV_BUILTIN_MCPLHI);
|
||
def_builtin ("__MCPLI", uw1_ftype_uw2_int, FRV_BUILTIN_MCPLI);
|
||
def_builtin ("__MDCUTSSI", uw2_ftype_acc_int, FRV_BUILTIN_MDCUTSSI);
|
||
def_builtin ("__MQSATHS", sw2_ftype_sw2_sw2, FRV_BUILTIN_MQSATHS);
|
||
def_builtin ("__MHSETLOS", sw1_ftype_sw1_int, FRV_BUILTIN_MHSETLOS);
|
||
def_builtin ("__MHSETHIS", sw1_ftype_sw1_int, FRV_BUILTIN_MHSETHIS);
|
||
def_builtin ("__MHDSETS", sw1_ftype_int, FRV_BUILTIN_MHDSETS);
|
||
def_builtin ("__MHSETLOH", uw1_ftype_uw1_int, FRV_BUILTIN_MHSETLOH);
|
||
def_builtin ("__MHSETHIH", uw1_ftype_uw1_int, FRV_BUILTIN_MHSETHIH);
|
||
def_builtin ("__MHDSETH", uw1_ftype_uw1_int, FRV_BUILTIN_MHDSETH);
|
||
|
||
#undef UNARY
|
||
#undef BINARY
|
||
#undef TRINARY
|
||
}
|
||
|
||
/* Convert an integer constant to an accumulator register. ICODE is the
|
||
code of the target instruction, OPNUM is the number of the
|
||
accumulator operand and OPVAL is the constant integer. Try both
|
||
ACC and ACCG registers; only report an error if neither fit the
|
||
instruction. */
|
||
|
||
static rtx
|
||
frv_int_to_acc (icode, opnum, opval)
|
||
enum insn_code icode;
|
||
int opnum;
|
||
rtx opval;
|
||
{
|
||
rtx reg;
|
||
|
||
if (GET_CODE (opval) != CONST_INT)
|
||
{
|
||
error ("accumulator is not a constant integer");
|
||
return NULL_RTX;
|
||
}
|
||
if (! IN_RANGE_P (INTVAL (opval), 0, NUM_ACCS - 1))
|
||
{
|
||
error ("accumulator number is out of bounds");
|
||
return NULL_RTX;
|
||
}
|
||
|
||
reg = gen_rtx_REG (insn_data[icode].operand[opnum].mode,
|
||
ACC_FIRST + INTVAL (opval));
|
||
if (! (*insn_data[icode].operand[opnum].predicate) (reg, VOIDmode))
|
||
REGNO (reg) = ACCG_FIRST + INTVAL (opval);
|
||
|
||
if (! (*insn_data[icode].operand[opnum].predicate) (reg, VOIDmode))
|
||
{
|
||
error ("inappropriate accumulator for `%s'", insn_data[icode].name);
|
||
return NULL_RTX;
|
||
}
|
||
return reg;
|
||
}
|
||
|
||
/* If an ACC rtx has mode MODE, return the mode that the matching ACCG
|
||
should have. */
|
||
|
||
static enum machine_mode
|
||
frv_matching_accg_mode (mode)
|
||
enum machine_mode mode;
|
||
{
|
||
switch (mode)
|
||
{
|
||
case V4SImode:
|
||
return V4QImode;
|
||
|
||
case DImode:
|
||
return HImode;
|
||
|
||
case SImode:
|
||
return QImode;
|
||
|
||
default:
|
||
abort ();
|
||
}
|
||
}
|
||
|
||
/* Return the accumulator guard that should be paired with accumulator
|
||
register ACC. The mode of the returned register is in the same
|
||
class as ACC, but is four times smaller. */
|
||
|
||
rtx
|
||
frv_matching_accg_for_acc (acc)
|
||
rtx acc;
|
||
{
|
||
return gen_rtx_REG (frv_matching_accg_mode (GET_MODE (acc)),
|
||
REGNO (acc) - ACC_FIRST + ACCG_FIRST);
|
||
}
|
||
|
||
/* Read a value from the head of the tree list pointed to by ARGLISTPTR.
|
||
Return the value as an rtx and replace *ARGLISTPTR with the tail of the
|
||
list. */
|
||
|
||
static rtx
|
||
frv_read_argument (arglistptr)
|
||
tree *arglistptr;
|
||
{
|
||
tree next = TREE_VALUE (*arglistptr);
|
||
*arglistptr = TREE_CHAIN (*arglistptr);
|
||
return expand_expr (next, NULL_RTX, VOIDmode, 0);
|
||
}
|
||
|
||
/* Return true if OPVAL can be used for operand OPNUM of instruction ICODE.
|
||
The instruction should require a constant operand of some sort. The
|
||
function prints an error if OPVAL is not valid. */
|
||
|
||
static int
|
||
frv_check_constant_argument (icode, opnum, opval)
|
||
enum insn_code icode;
|
||
int opnum;
|
||
rtx opval;
|
||
{
|
||
if (GET_CODE (opval) != CONST_INT)
|
||
{
|
||
error ("`%s' expects a constant argument", insn_data[icode].name);
|
||
return FALSE;
|
||
}
|
||
if (! (*insn_data[icode].operand[opnum].predicate) (opval, VOIDmode))
|
||
{
|
||
error ("constant argument out of range for `%s'", insn_data[icode].name);
|
||
return FALSE;
|
||
}
|
||
return TRUE;
|
||
}
|
||
|
||
/* Return a legitimate rtx for instruction ICODE's return value. Use TARGET
|
||
if it's not null, has the right mode, and satisfies operand 0's
|
||
predicate. */
|
||
|
||
static rtx
|
||
frv_legitimize_target (icode, target)
|
||
enum insn_code icode;
|
||
rtx target;
|
||
{
|
||
enum machine_mode mode = insn_data[icode].operand[0].mode;
|
||
|
||
if (! target
|
||
|| GET_MODE (target) != mode
|
||
|| ! (*insn_data[icode].operand[0].predicate) (target, mode))
|
||
return gen_reg_rtx (mode);
|
||
else
|
||
return target;
|
||
}
|
||
|
||
/* Given that ARG is being passed as operand OPNUM to instruction ICODE,
|
||
check whether ARG satisfies the operand's contraints. If it doesn't,
|
||
copy ARG to a temporary register and return that. Otherwise return ARG
|
||
itself. */
|
||
|
||
static rtx
|
||
frv_legitimize_argument (icode, opnum, arg)
|
||
enum insn_code icode;
|
||
int opnum;
|
||
rtx arg;
|
||
{
|
||
enum machine_mode mode = insn_data[icode].operand[opnum].mode;
|
||
|
||
if ((*insn_data[icode].operand[opnum].predicate) (arg, mode))
|
||
return arg;
|
||
else
|
||
return copy_to_mode_reg (mode, arg);
|
||
}
|
||
|
||
/* Expand builtins that take a single, constant argument. At the moment,
|
||
only MHDSETS falls into this category. */
|
||
|
||
static rtx
|
||
frv_expand_set_builtin (icode, arglist, target)
|
||
enum insn_code icode;
|
||
tree arglist;
|
||
rtx target;
|
||
{
|
||
rtx pat;
|
||
rtx op0 = frv_read_argument (&arglist);
|
||
|
||
if (! frv_check_constant_argument (icode, 1, op0))
|
||
return NULL_RTX;
|
||
|
||
target = frv_legitimize_target (icode, target);
|
||
pat = GEN_FCN (icode) (target, op0);
|
||
if (! pat)
|
||
return NULL_RTX;
|
||
|
||
emit_insn (pat);
|
||
return target;
|
||
}
|
||
|
||
/* Expand builtins that take one operand. */
|
||
|
||
static rtx
|
||
frv_expand_unop_builtin (icode, arglist, target)
|
||
enum insn_code icode;
|
||
tree arglist;
|
||
rtx target;
|
||
{
|
||
rtx pat;
|
||
rtx op0 = frv_read_argument (&arglist);
|
||
|
||
target = frv_legitimize_target (icode, target);
|
||
op0 = frv_legitimize_argument (icode, 1, op0);
|
||
pat = GEN_FCN (icode) (target, op0);
|
||
if (! pat)
|
||
return NULL_RTX;
|
||
|
||
emit_insn (pat);
|
||
return target;
|
||
}
|
||
|
||
/* Expand builtins that take two operands. */
|
||
|
||
static rtx
|
||
frv_expand_binop_builtin (icode, arglist, target)
|
||
enum insn_code icode;
|
||
tree arglist;
|
||
rtx target;
|
||
{
|
||
rtx pat;
|
||
rtx op0 = frv_read_argument (&arglist);
|
||
rtx op1 = frv_read_argument (&arglist);
|
||
|
||
target = frv_legitimize_target (icode, target);
|
||
op0 = frv_legitimize_argument (icode, 1, op0);
|
||
op1 = frv_legitimize_argument (icode, 2, op1);
|
||
pat = GEN_FCN (icode) (target, op0, op1);
|
||
if (! pat)
|
||
return NULL_RTX;
|
||
|
||
emit_insn (pat);
|
||
return target;
|
||
}
|
||
|
||
/* Expand cut-style builtins, which take two operands and an implicit ACCG
|
||
one. */
|
||
|
||
static rtx
|
||
frv_expand_cut_builtin (icode, arglist, target)
|
||
enum insn_code icode;
|
||
tree arglist;
|
||
rtx target;
|
||
{
|
||
rtx pat;
|
||
rtx op0 = frv_read_argument (&arglist);
|
||
rtx op1 = frv_read_argument (&arglist);
|
||
rtx op2;
|
||
|
||
target = frv_legitimize_target (icode, target);
|
||
op0 = frv_int_to_acc (icode, 1, op0);
|
||
if (! op0)
|
||
return NULL_RTX;
|
||
|
||
if (icode == CODE_FOR_mdcutssi || GET_CODE (op1) == CONST_INT)
|
||
{
|
||
if (! frv_check_constant_argument (icode, 2, op1))
|
||
return NULL_RTX;
|
||
}
|
||
else
|
||
op1 = frv_legitimize_argument (icode, 2, op1);
|
||
|
||
op2 = frv_matching_accg_for_acc (op0);
|
||
pat = GEN_FCN (icode) (target, op0, op1, op2);
|
||
if (! pat)
|
||
return NULL_RTX;
|
||
|
||
emit_insn (pat);
|
||
return target;
|
||
}
|
||
|
||
/* Expand builtins that take two operands and the second is immediate. */
|
||
|
||
static rtx
|
||
frv_expand_binopimm_builtin (icode, arglist, target)
|
||
enum insn_code icode;
|
||
tree arglist;
|
||
rtx target;
|
||
{
|
||
rtx pat;
|
||
rtx op0 = frv_read_argument (&arglist);
|
||
rtx op1 = frv_read_argument (&arglist);
|
||
|
||
if (! frv_check_constant_argument (icode, 2, op1))
|
||
return NULL_RTX;
|
||
|
||
target = frv_legitimize_target (icode, target);
|
||
op0 = frv_legitimize_argument (icode, 1, op0);
|
||
pat = GEN_FCN (icode) (target, op0, op1);
|
||
if (! pat)
|
||
return NULL_RTX;
|
||
|
||
emit_insn (pat);
|
||
return target;
|
||
}
|
||
|
||
/* Expand builtins that take two operands, the first operand being a pointer to
|
||
ints and return void. */
|
||
|
||
static rtx
|
||
frv_expand_voidbinop_builtin (icode, arglist)
|
||
enum insn_code icode;
|
||
tree arglist;
|
||
{
|
||
rtx pat;
|
||
rtx op0 = frv_read_argument (&arglist);
|
||
rtx op1 = frv_read_argument (&arglist);
|
||
enum machine_mode mode0 = insn_data[icode].operand[0].mode;
|
||
rtx addr;
|
||
|
||
if (GET_CODE (op0) != MEM)
|
||
{
|
||
rtx reg = op0;
|
||
|
||
if (! offsettable_address_p (0, mode0, op0))
|
||
{
|
||
reg = gen_reg_rtx (Pmode);
|
||
emit_insn (gen_rtx_SET (VOIDmode, reg, op0));
|
||
}
|
||
|
||
op0 = gen_rtx_MEM (SImode, reg);
|
||
}
|
||
|
||
addr = XEXP (op0, 0);
|
||
if (! offsettable_address_p (0, mode0, addr))
|
||
addr = copy_to_mode_reg (Pmode, op0);
|
||
|
||
op0 = change_address (op0, V4SImode, addr);
|
||
op1 = frv_legitimize_argument (icode, 1, op1);
|
||
pat = GEN_FCN (icode) (op0, op1);
|
||
if (! pat)
|
||
return 0;
|
||
|
||
emit_insn (pat);
|
||
return 0;
|
||
}
|
||
|
||
/* Expand builtins that take three operands and return void. The first
|
||
argument must be a constant that describes a pair or quad accumulators. A
|
||
fourth argument is created that is the accumulator guard register that
|
||
corresponds to the accumulator. */
|
||
|
||
static rtx
|
||
frv_expand_voidtriop_builtin (icode, arglist)
|
||
enum insn_code icode;
|
||
tree arglist;
|
||
{
|
||
rtx pat;
|
||
rtx op0 = frv_read_argument (&arglist);
|
||
rtx op1 = frv_read_argument (&arglist);
|
||
rtx op2 = frv_read_argument (&arglist);
|
||
rtx op3;
|
||
|
||
op0 = frv_int_to_acc (icode, 0, op0);
|
||
if (! op0)
|
||
return NULL_RTX;
|
||
|
||
op1 = frv_legitimize_argument (icode, 1, op1);
|
||
op2 = frv_legitimize_argument (icode, 2, op2);
|
||
op3 = frv_matching_accg_for_acc (op0);
|
||
pat = GEN_FCN (icode) (op0, op1, op2, op3);
|
||
if (! pat)
|
||
return NULL_RTX;
|
||
|
||
emit_insn (pat);
|
||
return NULL_RTX;
|
||
}
|
||
|
||
/* Expand builtins that perform accumulator-to-accumulator operations.
|
||
These builtins take two accumulator numbers as argument and return
|
||
void. */
|
||
|
||
static rtx
|
||
frv_expand_voidaccop_builtin (icode, arglist)
|
||
enum insn_code icode;
|
||
tree arglist;
|
||
{
|
||
rtx pat;
|
||
rtx op0 = frv_read_argument (&arglist);
|
||
rtx op1 = frv_read_argument (&arglist);
|
||
rtx op2;
|
||
rtx op3;
|
||
|
||
op0 = frv_int_to_acc (icode, 0, op0);
|
||
if (! op0)
|
||
return NULL_RTX;
|
||
|
||
op1 = frv_int_to_acc (icode, 1, op1);
|
||
if (! op1)
|
||
return NULL_RTX;
|
||
|
||
op2 = frv_matching_accg_for_acc (op0);
|
||
op3 = frv_matching_accg_for_acc (op1);
|
||
pat = GEN_FCN (icode) (op0, op1, op2, op3);
|
||
if (! pat)
|
||
return NULL_RTX;
|
||
|
||
emit_insn (pat);
|
||
return NULL_RTX;
|
||
}
|
||
|
||
/* Expand the MCLRACC builtin. This builtin takes a single accumulator
|
||
number as argument. */
|
||
|
||
static rtx
|
||
frv_expand_mclracc_builtin (arglist)
|
||
tree arglist;
|
||
{
|
||
enum insn_code icode = CODE_FOR_mclracc;
|
||
rtx pat;
|
||
rtx op0 = frv_read_argument (&arglist);
|
||
|
||
op0 = frv_int_to_acc (icode, 0, op0);
|
||
if (! op0)
|
||
return NULL_RTX;
|
||
|
||
pat = GEN_FCN (icode) (op0);
|
||
if (pat)
|
||
emit_insn (pat);
|
||
|
||
return NULL_RTX;
|
||
}
|
||
|
||
/* Expand builtins that take no arguments. */
|
||
|
||
static rtx
|
||
frv_expand_noargs_builtin (icode)
|
||
enum insn_code icode;
|
||
{
|
||
rtx pat = GEN_FCN (icode) (GEN_INT (0));
|
||
if (pat)
|
||
emit_insn (pat);
|
||
|
||
return NULL_RTX;
|
||
}
|
||
|
||
/* Expand MRDACC and MRDACCG. These builtins take a single accumulator
|
||
number or accumulator guard number as argument and return an SI integer. */
|
||
|
||
static rtx
|
||
frv_expand_mrdacc_builtin (icode, arglist)
|
||
enum insn_code icode;
|
||
tree arglist;
|
||
{
|
||
rtx pat;
|
||
rtx target = gen_reg_rtx (SImode);
|
||
rtx op0 = frv_read_argument (&arglist);
|
||
|
||
op0 = frv_int_to_acc (icode, 1, op0);
|
||
if (! op0)
|
||
return NULL_RTX;
|
||
|
||
pat = GEN_FCN (icode) (target, op0);
|
||
if (! pat)
|
||
return NULL_RTX;
|
||
|
||
emit_insn (pat);
|
||
return target;
|
||
}
|
||
|
||
/* Expand MWTACC and MWTACCG. These builtins take an accumulator or
|
||
accumulator guard as their first argument and an SImode value as their
|
||
second. */
|
||
|
||
static rtx
|
||
frv_expand_mwtacc_builtin (icode, arglist)
|
||
enum insn_code icode;
|
||
tree arglist;
|
||
{
|
||
rtx pat;
|
||
rtx op0 = frv_read_argument (&arglist);
|
||
rtx op1 = frv_read_argument (&arglist);
|
||
|
||
op0 = frv_int_to_acc (icode, 0, op0);
|
||
if (! op0)
|
||
return NULL_RTX;
|
||
|
||
op1 = frv_legitimize_argument (icode, 1, op1);
|
||
pat = GEN_FCN (icode) (op0, op1);
|
||
if (pat)
|
||
emit_insn (pat);
|
||
|
||
return NULL_RTX;
|
||
}
|
||
|
||
/* Expand builtins. */
|
||
|
||
static rtx
|
||
frv_expand_builtin (exp, target, subtarget, mode, ignore)
|
||
tree exp;
|
||
rtx target;
|
||
rtx subtarget ATTRIBUTE_UNUSED;
|
||
enum machine_mode mode ATTRIBUTE_UNUSED;
|
||
int ignore ATTRIBUTE_UNUSED;
|
||
{
|
||
tree arglist = TREE_OPERAND (exp, 1);
|
||
tree fndecl = TREE_OPERAND (TREE_OPERAND (exp, 0), 0);
|
||
unsigned fcode = (unsigned)DECL_FUNCTION_CODE (fndecl);
|
||
unsigned i;
|
||
struct builtin_description *d;
|
||
|
||
if (! TARGET_MEDIA)
|
||
{
|
||
error ("media functions are not available unless -mmedia is used");
|
||
return NULL_RTX;
|
||
}
|
||
|
||
switch (fcode)
|
||
{
|
||
case FRV_BUILTIN_MCOP1:
|
||
case FRV_BUILTIN_MCOP2:
|
||
case FRV_BUILTIN_MDUNPACKH:
|
||
case FRV_BUILTIN_MBTOHE:
|
||
if (! TARGET_MEDIA_REV1)
|
||
{
|
||
error ("this media function is only available on the fr500");
|
||
return NULL_RTX;
|
||
}
|
||
break;
|
||
|
||
case FRV_BUILTIN_MQXMACHS:
|
||
case FRV_BUILTIN_MQXMACXHS:
|
||
case FRV_BUILTIN_MQMACXHS:
|
||
case FRV_BUILTIN_MADDACCS:
|
||
case FRV_BUILTIN_MSUBACCS:
|
||
case FRV_BUILTIN_MASACCS:
|
||
case FRV_BUILTIN_MDADDACCS:
|
||
case FRV_BUILTIN_MDSUBACCS:
|
||
case FRV_BUILTIN_MDASACCS:
|
||
case FRV_BUILTIN_MABSHS:
|
||
case FRV_BUILTIN_MDROTLI:
|
||
case FRV_BUILTIN_MCPLHI:
|
||
case FRV_BUILTIN_MCPLI:
|
||
case FRV_BUILTIN_MDCUTSSI:
|
||
case FRV_BUILTIN_MQSATHS:
|
||
case FRV_BUILTIN_MHSETLOS:
|
||
case FRV_BUILTIN_MHSETLOH:
|
||
case FRV_BUILTIN_MHSETHIS:
|
||
case FRV_BUILTIN_MHSETHIH:
|
||
case FRV_BUILTIN_MHDSETS:
|
||
case FRV_BUILTIN_MHDSETH:
|
||
if (! TARGET_MEDIA_REV2)
|
||
{
|
||
error ("this media function is only available on the fr400");
|
||
return NULL_RTX;
|
||
}
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
|
||
/* Expand unique builtins. */
|
||
|
||
switch (fcode)
|
||
{
|
||
case FRV_BUILTIN_MTRAP:
|
||
return frv_expand_noargs_builtin (CODE_FOR_mtrap);
|
||
|
||
case FRV_BUILTIN_MCLRACC:
|
||
return frv_expand_mclracc_builtin (arglist);
|
||
|
||
case FRV_BUILTIN_MCLRACCA:
|
||
if (TARGET_ACC_8)
|
||
return frv_expand_noargs_builtin (CODE_FOR_mclracca8);
|
||
else
|
||
return frv_expand_noargs_builtin (CODE_FOR_mclracca4);
|
||
|
||
case FRV_BUILTIN_MRDACC:
|
||
return frv_expand_mrdacc_builtin (CODE_FOR_mrdacc, arglist);
|
||
|
||
case FRV_BUILTIN_MRDACCG:
|
||
return frv_expand_mrdacc_builtin (CODE_FOR_mrdaccg, arglist);
|
||
|
||
case FRV_BUILTIN_MWTACC:
|
||
return frv_expand_mwtacc_builtin (CODE_FOR_mwtacc, arglist);
|
||
|
||
case FRV_BUILTIN_MWTACCG:
|
||
return frv_expand_mwtacc_builtin (CODE_FOR_mwtaccg, arglist);
|
||
|
||
default:
|
||
break;
|
||
}
|
||
|
||
/* Expand groups of builtins. */
|
||
|
||
for (i = 0, d = bdesc_set; i < sizeof (bdesc_set) / sizeof *d; i++, d++)
|
||
if (d->code == fcode)
|
||
return frv_expand_set_builtin (d->icode, arglist, target);
|
||
|
||
for (i = 0, d = bdesc_1arg; i < sizeof (bdesc_1arg) / sizeof *d; i++, d++)
|
||
if (d->code == fcode)
|
||
return frv_expand_unop_builtin (d->icode, arglist, target);
|
||
|
||
for (i = 0, d = bdesc_2arg; i < sizeof (bdesc_2arg) / sizeof *d; i++, d++)
|
||
if (d->code == fcode)
|
||
return frv_expand_binop_builtin (d->icode, arglist, target);
|
||
|
||
for (i = 0, d = bdesc_cut; i < sizeof (bdesc_cut) / sizeof *d; i++, d++)
|
||
if (d->code == fcode)
|
||
return frv_expand_cut_builtin (d->icode, arglist, target);
|
||
|
||
for (i = 0, d = bdesc_2argimm;
|
||
i < sizeof (bdesc_2argimm) / sizeof *d;
|
||
i++, d++)
|
||
{
|
||
if (d->code == fcode)
|
||
return frv_expand_binopimm_builtin (d->icode, arglist, target);
|
||
}
|
||
|
||
for (i = 0, d = bdesc_void2arg;
|
||
i < sizeof (bdesc_void2arg) / sizeof *d;
|
||
i++, d++)
|
||
{
|
||
if (d->code == fcode)
|
||
return frv_expand_voidbinop_builtin (d->icode, arglist);
|
||
}
|
||
|
||
for (i = 0, d = bdesc_void3arg;
|
||
i < sizeof (bdesc_void3arg) / sizeof *d;
|
||
i++, d++)
|
||
{
|
||
if (d->code == fcode)
|
||
return frv_expand_voidtriop_builtin (d->icode, arglist);
|
||
}
|
||
|
||
for (i = 0, d = bdesc_voidacc;
|
||
i < sizeof (bdesc_voidacc) / sizeof *d;
|
||
i++, d++)
|
||
{
|
||
if (d->code == fcode)
|
||
return frv_expand_voidaccop_builtin (d->icode, arglist);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static const char *
|
||
frv_strip_name_encoding (str)
|
||
const char *str;
|
||
{
|
||
while (*str == '*' || *str == SDATA_FLAG_CHAR)
|
||
str++;
|
||
return str;
|
||
}
|
||
|
||
static bool
|
||
frv_in_small_data_p (decl)
|
||
tree decl;
|
||
{
|
||
HOST_WIDE_INT size = int_size_in_bytes (TREE_TYPE (decl));
|
||
|
||
return symbol_ref_small_data_p (XEXP (DECL_RTL (decl), 0))
|
||
&& size > 0 && size <= g_switch_value;
|
||
}
|