blob: 44c494ee99e09edbba9889ed708985a20828eaab [file] [log] [blame]
/* Target definitions for the MorphoRISC1
Copyright (C) 2005 Free Software Foundation, Inc.
Contributed by Red Hat, Inc.
This file is part of GCC.
GCC is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published
by the Free Software Foundation; either version 2, or (at your
option) any later version.
GCC is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING. If not, write to the Free
Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA. */
#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "tm.h"
#include "rtl.h"
#include "regs.h"
#include "hard-reg-set.h"
#include "real.h"
#include "insn-config.h"
#include "conditions.h"
#include "insn-attr.h"
#include "recog.h"
#include "toplev.h"
#include "output.h"
#include "integrate.h"
#include "tree.h"
#include "function.h"
#include "expr.h"
#include "optabs.h"
#include "libfuncs.h"
#include "flags.h"
#include "tm_p.h"
#include "ggc.h"
#include "insn-flags.h"
#include "obstack.h"
#include "except.h"
#include "target.h"
#include "target-def.h"
#include "basic-block.h"
/* Frame pointer register mask. */
#define FP_MASK (1 << (GPR_FP))
/* Link register mask. */
#define LINK_MASK (1 << (GPR_LINK))
/* Given a SIZE in bytes, advance to the next word. */
#define ROUND_ADVANCE(SIZE) (((SIZE) + UNITS_PER_WORD - 1) / UNITS_PER_WORD)
/* A C structure for machine-specific, per-function data.
This is added to the cfun structure. */
struct machine_function GTY(())
{
/* Flags if __builtin_return_address (n) with n >= 1 was used. */
int ra_needs_full_frame;
struct rtx_def * eh_stack_adjust;
int interrupt_handler;
int has_loops;
};
/* Define the information needed to generate branch and scc insns.
This is stored from the compare operation. */
struct rtx_def * mt_compare_op0;
struct rtx_def * mt_compare_op1;
/* Current frame information calculated by compute_frame_size. */
struct mt_frame_info current_frame_info;
/* Zero structure to initialize current_frame_info. */
struct mt_frame_info zero_frame_info;
/* mt doesn't have unsigned compares need a library call for this. */
struct rtx_def * mt_ucmpsi3_libcall;
static int mt_flag_delayed_branch;
static rtx
mt_struct_value_rtx (tree fndecl ATTRIBUTE_UNUSED,
int incoming ATTRIBUTE_UNUSED)
{
return gen_rtx_REG (Pmode, RETVAL_REGNUM);
}
/* Implement RETURN_ADDR_RTX. */
rtx
mt_return_addr_rtx (int count)
{
if (count != 0)
return NULL_RTX;
return get_hard_reg_initial_val (Pmode, GPR_LINK);
}
/* The following variable value indicates the number of nops required
between the current instruction and the next instruction to avoid
any pipeline hazards. */
static int mt_nops_required = 0;
static const char * mt_nop_reasons = "";
/* Implement ASM_OUTPUT_OPCODE. */
const char *
mt_asm_output_opcode (FILE *f ATTRIBUTE_UNUSED, const char *ptr)
{
if (mt_nops_required)
fprintf (f, ";# need %d nops because of %s\n\t",
mt_nops_required, mt_nop_reasons);
while (mt_nops_required)
{
fprintf (f, "nop\n\t");
-- mt_nops_required;
}
return ptr;
}
/* Given an insn, return whether it's a memory operation or a branch
operation, otherwise return TYPE_ARITH. */
static enum attr_type
mt_get_attr_type (rtx complete_insn)
{
rtx insn = PATTERN (complete_insn);
if (JUMP_P (complete_insn))
return TYPE_BRANCH;
if (CALL_P (complete_insn))
return TYPE_BRANCH;
if (GET_CODE (insn) != SET)
return TYPE_ARITH;
if (SET_DEST (insn) == pc_rtx)
return TYPE_BRANCH;
if (GET_CODE (SET_DEST (insn)) == MEM)
return TYPE_STORE;
if (GET_CODE (SET_SRC (insn)) == MEM)
return TYPE_LOAD;
return TYPE_ARITH;
}
/* A helper routine for insn_dependent_p called through note_stores. */
static void
insn_dependent_p_1 (rtx x, rtx pat ATTRIBUTE_UNUSED, void *data)
{
rtx * pinsn = (rtx *) data;
if (*pinsn && reg_mentioned_p (x, *pinsn))
*pinsn = NULL_RTX;
}
/* Return true if anything in insn X is (anti,output,true)
dependent on anything in insn Y. */
static bool
insn_dependent_p (rtx x, rtx y)
{
rtx tmp;
if (! INSN_P (x) || ! INSN_P (y))
return 0;
tmp = PATTERN (y);
note_stores (PATTERN (x), insn_dependent_p_1, &tmp);
if (tmp == NULL_RTX)
return true;
tmp = PATTERN (x);
note_stores (PATTERN (y), insn_dependent_p_1, &tmp);
return (tmp == NULL_RTX);
}
/* Return true if anything in insn X is true dependent on anything in
insn Y. */
static bool
insn_true_dependent_p (rtx x, rtx y)
{
rtx tmp;
if (! INSN_P (x) || ! INSN_P (y))
return 0;
tmp = PATTERN (y);
note_stores (PATTERN (x), insn_dependent_p_1, &tmp);
return (tmp == NULL_RTX);
}
/* The following determines the number of nops that need to be
inserted between the previous instructions and current instruction
to avoid pipeline hazards on the mt processor. Remember that
the function is not called for asm insns. */
void
mt_final_prescan_insn (rtx insn,
rtx * opvec ATTRIBUTE_UNUSED,
int noperands ATTRIBUTE_UNUSED)
{
rtx prev_i;
enum attr_type prev_attr;
mt_nops_required = 0;
mt_nop_reasons = "";
/* ms2 constraints are dealt with in reorg. */
if (TARGET_MS2)
return;
/* Only worry about real instructions. */
if (! INSN_P (insn))
return;
/* Find the previous real instructions. */
for (prev_i = PREV_INSN (insn);
prev_i != NULL
&& (! INSN_P (prev_i)
|| GET_CODE (PATTERN (prev_i)) == USE
|| GET_CODE (PATTERN (prev_i)) == CLOBBER);
prev_i = PREV_INSN (prev_i))
{
/* If we meet a barrier, there is no flow through here. */
if (BARRIER_P (prev_i))
return;
}
/* If there isn't one then there is nothing that we need do. */
if (prev_i == NULL || ! INSN_P (prev_i))
return;
prev_attr = mt_get_attr_type (prev_i);
/* Delayed branch slots already taken care of by delay branch scheduling. */
if (prev_attr == TYPE_BRANCH)
return;
switch (mt_get_attr_type (insn))
{
case TYPE_LOAD:
case TYPE_STORE:
/* Avoid consecutive memory operation. */
if ((prev_attr == TYPE_LOAD || prev_attr == TYPE_STORE)
&& TARGET_MS1_64_001)
{
mt_nops_required = 1;
mt_nop_reasons = "consecutive mem ops";
}
/* Drop through. */
case TYPE_ARITH:
case TYPE_COMPLEX:
/* One cycle of delay is required between load
and the dependent arithmetic instruction. */
if (prev_attr == TYPE_LOAD
&& insn_true_dependent_p (prev_i, insn))
{
mt_nops_required = 1;
mt_nop_reasons = "load->arith dependency delay";
}
break;
case TYPE_BRANCH:
if (insn_dependent_p (prev_i, insn))
{
if (prev_attr == TYPE_ARITH && TARGET_MS1_64_001)
{
/* One cycle of delay between arith
instructions and branch dependent on arith. */
mt_nops_required = 1;
mt_nop_reasons = "arith->branch dependency delay";
}
else if (prev_attr == TYPE_LOAD)
{
/* Two cycles of delay are required
between load and dependent branch. */
if (TARGET_MS1_64_001)
mt_nops_required = 2;
else
mt_nops_required = 1;
mt_nop_reasons = "load->branch dependency delay";
}
}
break;
default:
fatal_insn ("mt_final_prescan_insn, invalid insn #1", insn);
break;
}
}
/* Print debugging information for a frame. */
static void
mt_debug_stack (struct mt_frame_info * info)
{
int regno;
if (!info)
{
error ("info pointer NULL");
gcc_unreachable ();
}
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 = %d\n", info->total_size);
fprintf (stderr, "\tpretend_size = %d\n", info->pretend_size);
fprintf (stderr, "\targs_size = %d\n", info->args_size);
fprintf (stderr, "\textra_size = %d\n", info->extra_size);
fprintf (stderr, "\treg_size = %d\n", info->reg_size);
fprintf (stderr, "\tvar_size = %d\n", info->var_size);
fprintf (stderr, "\tframe_size = %d\n", info->frame_size);
fprintf (stderr, "\treg_mask = 0x%x\n", info->reg_mask);
fprintf (stderr, "\tsave_fp = %d\n", info->save_fp);
fprintf (stderr, "\tsave_lr = %d\n", info->save_lr);
fprintf (stderr, "\tinitialized = %d\n", info->initialized);
fprintf (stderr, "\tsaved registers =");
/* Print out reg_mask in a more readable format. */
for (regno = GPR_R0; regno <= GPR_LAST; regno++)
if ( (1 << regno) & info->reg_mask)
fprintf (stderr, " %s", reg_names[regno]);
putc ('\n', stderr);
fflush (stderr);
}
/* Print a memory address as an operand to reference that memory location. */
static void
mt_print_operand_simple_address (FILE * file, rtx addr)
{
if (!addr)
error ("PRINT_OPERAND_ADDRESS, null pointer");
else
switch (GET_CODE (addr))
{
case REG:
fprintf (file, "%s, #0", reg_names [REGNO (addr)]);
break;
case PLUS:
{
rtx reg = 0;
rtx offset = 0;
rtx arg0 = XEXP (addr, 0);
rtx arg1 = XEXP (addr, 1);
if (GET_CODE (arg0) == REG)
{
reg = arg0;
offset = arg1;
if (GET_CODE (offset) == REG)
fatal_insn ("PRINT_OPERAND_ADDRESS, 2 regs", addr);
}
else if (GET_CODE (arg1) == REG)
reg = arg1, offset = arg0;
else if (CONSTANT_P (arg0) && CONSTANT_P (arg1))
{
fprintf (file, "%s, #", reg_names [GPR_R0]);
output_addr_const (file, addr);
break;
}
fprintf (file, "%s, #", reg_names [REGNO (reg)]);
output_addr_const (file, offset);
break;
}
case LABEL_REF:
case SYMBOL_REF:
case CONST_INT:
case CONST:
output_addr_const (file, addr);
break;
default:
fatal_insn ("PRINT_OPERAND_ADDRESS, invalid insn #1", addr);
break;
}
}
/* Implement PRINT_OPERAND_ADDRESS. */
void
mt_print_operand_address (FILE * file, rtx addr)
{
if (GET_CODE (addr) == AND
&& GET_CODE (XEXP (addr, 1)) == CONST_INT
&& INTVAL (XEXP (addr, 1)) == -3)
mt_print_operand_simple_address (file, XEXP (addr, 0));
else
mt_print_operand_simple_address (file, addr);
}
/* Implement PRINT_OPERAND. */
void
mt_print_operand (FILE * file, rtx x, int code)
{
switch (code)
{
case '#':
/* Output a nop if there's nothing for the delay slot. */
if (dbr_sequence_length () == 0)
fputs ("\n\tnop", file);
return;
case 'H':
fprintf(file, "#%%hi16(");
output_addr_const (file, x);
fprintf(file, ")");
return;
case 'L':
fprintf(file, "#%%lo16(");
output_addr_const (file, x);
fprintf(file, ")");
return;
case 'N':
fprintf(file, "#%ld", ~INTVAL (x));
return;
case 'z':
if (GET_CODE (x) == CONST_INT && INTVAL (x) == 0)
{
fputs (reg_names[GPR_R0], file);
return;
}
case 0:
/* Handled below. */
break;
default:
/* output_operand_lossage ("mt_print_operand: unknown code"); */
fprintf (file, "unknown code");
return;
}
switch (GET_CODE (x))
{
case REG:
fputs (reg_names [REGNO (x)], file);
break;
case CONST:
case CONST_INT:
fprintf(file, "#%ld", INTVAL (x));
break;
case MEM:
mt_print_operand_address(file, XEXP (x,0));
break;
case LABEL_REF:
case SYMBOL_REF:
output_addr_const (file, x);
break;
default:
fprintf(file, "Uknown code: %d", GET_CODE (x));
break;
}
return;
}
/* Implement INIT_CUMULATIVE_ARGS. */
void
mt_init_cumulative_args (CUMULATIVE_ARGS * cum, tree fntype, rtx libname,
tree fndecl ATTRIBUTE_UNUSED, int incoming)
{
*cum = 0;
if (TARGET_DEBUG_ARG)
{
fprintf (stderr, "\nmt_init_cumulative_args:");
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);
}
}
/* Compute the slot number to pass an argument in.
Returns the slot number or -1 if passing on the stack.
CUM is a variable of type CUMULATIVE_ARGS which gives info about
the preceding args and about the function being called.
MODE is the argument's machine mode.
TYPE is the data type of the argument (as a tree).
This is null for libcalls where that information may
not be available.
NAMED is nonzero if this argument is a named parameter
(otherwise it is an extra parameter matching an ellipsis).
INCOMING_P is zero for FUNCTION_ARG, nonzero for FUNCTION_INCOMING_ARG.
*PREGNO records the register number to use if scalar type. */
static int
mt_function_arg_slotno (const CUMULATIVE_ARGS * cum,
enum machine_mode mode,
tree type,
int named ATTRIBUTE_UNUSED,
int incoming_p ATTRIBUTE_UNUSED,
int * pregno)
{
int regbase = FIRST_ARG_REGNUM;
int slotno = * cum;
if (mode == VOIDmode || targetm.calls.must_pass_in_stack (mode, type))
return -1;
if (slotno >= MT_NUM_ARG_REGS)
return -1;
* pregno = regbase + slotno;
return slotno;
}
/* Implement FUNCTION_ARG. */
rtx
mt_function_arg (const CUMULATIVE_ARGS * cum,
enum machine_mode mode,
tree type,
int named,
int incoming_p)
{
int slotno, regno;
rtx reg;
slotno = mt_function_arg_slotno (cum, mode, type, named, incoming_p, &regno);
if (slotno == -1)
reg = NULL_RTX;
else
reg = gen_rtx_REG (mode, regno);
return reg;
}
/* Implement FUNCTION_ARG_ADVANCE. */
void
mt_function_arg_advance (CUMULATIVE_ARGS * cum,
enum machine_mode mode,
tree type ATTRIBUTE_UNUSED,
int named)
{
int slotno, regno;
/* We pass 0 for incoming_p here, it doesn't matter. */
slotno = mt_function_arg_slotno (cum, mode, type, named, 0, &regno);
* cum += (mode != BLKmode
? ROUND_ADVANCE (GET_MODE_SIZE (mode))
: ROUND_ADVANCE (int_size_in_bytes (type)));
if (TARGET_DEBUG_ARG)
fprintf (stderr,
"mt_function_arg_advance: words = %2d, mode = %4s, named = %d, size = %3d\n",
*cum, GET_MODE_NAME (mode), named,
(*cum) * UNITS_PER_WORD);
}
/* Implement hook TARGET_ARG_PARTIAL_BYTES.
Returns the number of bytes at the beginning of an argument that
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. */
static int
mt_arg_partial_bytes (CUMULATIVE_ARGS * pcum,
enum machine_mode mode,
tree type,
bool named ATTRIBUTE_UNUSED)
{
int cum = * pcum;
int words;
if (mode == BLKmode)
words = ((int_size_in_bytes (type) + UNITS_PER_WORD - 1)
/ UNITS_PER_WORD);
else
words = (GET_MODE_SIZE (mode) + UNITS_PER_WORD - 1) / UNITS_PER_WORD;
if (! targetm.calls.pass_by_reference (&cum, mode, type, named)
&& cum < MT_NUM_ARG_REGS
&& (cum + words) > MT_NUM_ARG_REGS)
{
int bytes = (MT_NUM_ARG_REGS - cum) * UNITS_PER_WORD;
if (TARGET_DEBUG)
fprintf (stderr, "function_arg_partial_nregs = %d\n", bytes);
return bytes;
}
return 0;
}
/* Implement TARGET_PASS_BY_REFERENCE hook. */
static bool
mt_pass_by_reference (CUMULATIVE_ARGS * cum ATTRIBUTE_UNUSED,
enum machine_mode mode ATTRIBUTE_UNUSED,
tree type,
bool named ATTRIBUTE_UNUSED)
{
return (type && int_size_in_bytes (type) > 4 * UNITS_PER_WORD);
}
/* Implement FUNCTION_ARG_BOUNDARY. */
int
mt_function_arg_boundary (enum machine_mode mode ATTRIBUTE_UNUSED,
tree type ATTRIBUTE_UNUSED)
{
return BITS_PER_WORD;
}
/* Implement REG_OK_FOR_BASE_P. */
int
mt_reg_ok_for_base_p (rtx x, int strict)
{
if (strict)
return (((unsigned) REGNO (x)) < FIRST_PSEUDO_REGISTER);
return 1;
}
/* Helper function of mt_legitimate_address_p. Return true if XINSN
is a simple address, otherwise false. */
static bool
mt_legitimate_simple_address_p (enum machine_mode mode ATTRIBUTE_UNUSED,
rtx xinsn, int strict)
{
if (TARGET_DEBUG)
{
fprintf (stderr, "\n========== GO_IF_LEGITIMATE_ADDRESS, %sstrict\n",
strict ? "" : "not ");
debug_rtx (xinsn);
}
if (GET_CODE (xinsn) == REG && mt_reg_ok_for_base_p (xinsn, strict))
return true;
if (GET_CODE (xinsn) == PLUS
&& GET_CODE (XEXP (xinsn, 0)) == REG
&& mt_reg_ok_for_base_p (XEXP (xinsn, 0), strict)
&& GET_CODE (XEXP (xinsn, 1)) == CONST_INT
&& SMALL_INT (XEXP (xinsn, 1)))
return true;
return false;
}
/* Helper function of GO_IF_LEGITIMATE_ADDRESS. Return nonzero if
XINSN is a legitimate address on MT. */
int
mt_legitimate_address_p (enum machine_mode mode, rtx xinsn, int strict)
{
if (mt_legitimate_simple_address_p (mode, xinsn, strict))
return 1;
if ((mode) == SImode
&& GET_CODE (xinsn) == AND
&& GET_CODE (XEXP (xinsn, 1)) == CONST_INT
&& INTVAL (XEXP (xinsn, 1)) == -3)
return mt_legitimate_simple_address_p (mode, XEXP (xinsn, 0), strict);
else
return 0;
}
/* Return truth value of whether OP can be used as an operands where a
register or 16 bit unsigned integer is needed. */
int
uns_arith_operand (rtx op, enum machine_mode mode)
{
if (GET_CODE (op) == CONST_INT && SMALL_INT_UNSIGNED (op))
return 1;
return register_operand (op, mode);
}
/* Return truth value of whether OP can be used as an operands where a
16 bit integer is needed. */
int
arith_operand (rtx op, enum machine_mode mode)
{
if (GET_CODE (op) == CONST_INT && SMALL_INT (op))
return 1;
return register_operand (op, mode);
}
/* Return truth value of whether OP is a register or the constant 0. */
int
reg_or_0_operand (rtx op, enum machine_mode mode)
{
switch (GET_CODE (op))
{
case CONST_INT:
return INTVAL (op) == 0;
case REG:
case SUBREG:
return register_operand (op, mode);
default:
break;
}
return 0;
}
/* Return truth value of whether OP is a constant that requires two
loads to put in a register. */
int
big_const_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED)
{
if (GET_CODE (op) == CONST_INT && CONST_OK_FOR_LETTER_P (INTVAL (op), 'M'))
return 1;
return 0;
}
/* Return truth value of whether OP is a constant that require only
one load to put in a register. */
int
single_const_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED)
{
if (big_const_operand (op, mode)
|| GET_CODE (op) == CONST
|| GET_CODE (op) == LABEL_REF
|| GET_CODE (op) == SYMBOL_REF)
return 0;
return 1;
}
/* True if the current function is an interrupt handler
(either via #pragma or an attribute specification). */
int interrupt_handler;
enum processor_type mt_cpu;
static struct machine_function *
mt_init_machine_status (void)
{
struct machine_function *f;
f = ggc_alloc_cleared (sizeof (struct machine_function));
return f;
}
/* Implement OVERRIDE_OPTIONS. */
void
mt_override_options (void)
{
if (mt_cpu_string != NULL)
{
if (!strcmp (mt_cpu_string, "ms1-64-001"))
mt_cpu = PROCESSOR_MS1_64_001;
else if (!strcmp (mt_cpu_string, "ms1-16-002"))
mt_cpu = PROCESSOR_MS1_16_002;
else if (!strcmp (mt_cpu_string, "ms1-16-003"))
mt_cpu = PROCESSOR_MS1_16_003;
else if (!strcmp (mt_cpu_string, "ms2"))
mt_cpu = PROCESSOR_MS2;
else
error ("bad value (%s) for -march= switch", mt_cpu_string);
}
else
mt_cpu = PROCESSOR_MS1_16_002;
if (flag_exceptions)
{
flag_omit_frame_pointer = 0;
flag_gcse = 0;
}
/* We do delayed branch filling in machine dependent reorg */
mt_flag_delayed_branch = flag_delayed_branch;
flag_delayed_branch = 0;
init_machine_status = mt_init_machine_status;
}
/* Do what is necessary for `va_start'. We look at the current function
to determine if stdarg or varargs is used and return the address of the
first unnamed parameter. */
static void
mt_setup_incoming_varargs (CUMULATIVE_ARGS *cum,
enum machine_mode mode ATTRIBUTE_UNUSED,
tree type ATTRIBUTE_UNUSED,
int *pretend_size, int no_rtl)
{
int regno;
int regs = MT_NUM_ARG_REGS - *cum;
*pretend_size = regs < 0 ? 0 : GET_MODE_SIZE (SImode) * regs;
if (no_rtl)
return;
for (regno = *cum; regno < MT_NUM_ARG_REGS; regno++)
{
rtx reg = gen_rtx_REG (SImode, FIRST_ARG_REGNUM + regno);
rtx slot = gen_rtx_PLUS (Pmode,
gen_rtx_REG (SImode, ARG_POINTER_REGNUM),
GEN_INT (UNITS_PER_WORD * regno));
emit_move_insn (gen_rtx_MEM (SImode, slot), reg);
}
}
/* Returns the number of bytes offset between the frame pointer and the stack
pointer for the current function. SIZE is the number of bytes of space
needed for local variables. */
unsigned int
mt_compute_frame_size (int size)
{
int regno;
unsigned int total_size;
unsigned int var_size;
unsigned int args_size;
unsigned int pretend_size;
unsigned int extra_size;
unsigned int reg_size;
unsigned int frame_size;
unsigned int reg_mask;
var_size = size;
args_size = current_function_outgoing_args_size;
pretend_size = current_function_pretend_args_size;
extra_size = FIRST_PARM_OFFSET (0);
total_size = extra_size + pretend_size + args_size + var_size;
reg_size = 0;
reg_mask = 0;
/* Calculate space needed for registers. */
for (regno = GPR_R0; regno <= GPR_LAST; regno++)
{
if (MUST_SAVE_REGISTER (regno))
{
reg_size += UNITS_PER_WORD;
reg_mask |= 1 << regno;
}
}
current_frame_info.save_fp = (regs_ever_live [GPR_FP]
|| frame_pointer_needed
|| interrupt_handler);
current_frame_info.save_lr = (regs_ever_live [GPR_LINK]
|| profile_flag
|| interrupt_handler);
reg_size += (current_frame_info.save_fp + current_frame_info.save_lr)
* UNITS_PER_WORD;
total_size += reg_size;
total_size = ((total_size + 3) & ~3);
frame_size = total_size;
/* Save computed information. */
current_frame_info.pretend_size = pretend_size;
current_frame_info.var_size = var_size;
current_frame_info.args_size = args_size;
current_frame_info.reg_size = reg_size;
current_frame_info.frame_size = args_size + var_size;
current_frame_info.total_size = total_size;
current_frame_info.extra_size = extra_size;
current_frame_info.reg_mask = reg_mask;
current_frame_info.initialized = reload_completed;
return total_size;
}
/* Emit code to save REG in stack offset pointed to by MEM.
STACK_OFFSET is the offset from the SP where the save will happen.
This function sets the REG_FRAME_RELATED_EXPR note accordingly. */
static void
mt_emit_save_restore (enum save_direction direction,
rtx reg, rtx mem, int stack_offset)
{
if (direction == FROM_PROCESSOR_TO_MEM)
{
rtx insn;
insn = emit_move_insn (mem, reg);
RTX_FRAME_RELATED_P (insn) = 1;
REG_NOTES (insn)
= gen_rtx_EXPR_LIST
(REG_FRAME_RELATED_EXPR,
gen_rtx_SET (VOIDmode,
gen_rtx_MEM (SImode,
gen_rtx_PLUS (SImode,
stack_pointer_rtx,
GEN_INT (stack_offset))),
reg),
REG_NOTES (insn));
}
else
emit_move_insn (reg, mem);
}
/* Emit code to save the frame pointer in the prologue and restore
frame pointer in epilogue. */
static void
mt_emit_save_fp (enum save_direction direction,
struct mt_frame_info info)
{
rtx base_reg;
int reg_mask = info.reg_mask & ~(FP_MASK | LINK_MASK);
int offset = info.total_size;
int stack_offset = info.total_size;
/* If there is nothing to save, get out now. */
if (! info.save_fp && ! info.save_lr && ! reg_mask)
return;
/* If offset doesn't fit in a 15-bit signed integer,
uses a scratch registers to get a smaller offset. */
if (CONST_OK_FOR_LETTER_P(offset, 'O'))
base_reg = stack_pointer_rtx;
else
{
/* Use the scratch register R9 that holds old stack pointer. */
base_reg = gen_rtx_REG (SImode, GPR_R9);
offset = 0;
}
if (info.save_fp)
{
offset -= UNITS_PER_WORD;
stack_offset -= UNITS_PER_WORD;
mt_emit_save_restore
(direction, gen_rtx_REG (SImode, GPR_FP),
gen_rtx_MEM (SImode,
gen_rtx_PLUS (SImode, base_reg, GEN_INT (offset))),
stack_offset);
}
}
/* Emit code to save registers in the prologue and restore register
in epilogue. */
static void
mt_emit_save_regs (enum save_direction direction,
struct mt_frame_info info)
{
rtx base_reg;
int regno;
int reg_mask = info.reg_mask & ~(FP_MASK | LINK_MASK);
int offset = info.total_size;
int stack_offset = info.total_size;
/* If there is nothing to save, get out now. */
if (! info.save_fp && ! info.save_lr && ! reg_mask)
return;
/* If offset doesn't fit in a 15-bit signed integer,
uses a scratch registers to get a smaller offset. */
if (CONST_OK_FOR_LETTER_P(offset, 'O'))
base_reg = stack_pointer_rtx;
else
{
/* Use the scratch register R9 that holds old stack pointer. */
base_reg = gen_rtx_REG (SImode, GPR_R9);
offset = 0;
}
if (info.save_fp)
{
/* This just records the space for it, the actual move generated in
mt_emit_save_fp (). */
offset -= UNITS_PER_WORD;
stack_offset -= UNITS_PER_WORD;
}
if (info.save_lr)
{
offset -= UNITS_PER_WORD;
stack_offset -= UNITS_PER_WORD;
mt_emit_save_restore
(direction, gen_rtx_REG (SImode, GPR_LINK),
gen_rtx_MEM (SImode,
gen_rtx_PLUS (SImode, base_reg, GEN_INT (offset))),
stack_offset);
}
/* Save any needed call-saved regs. */
for (regno = GPR_R0; regno <= GPR_LAST; regno++)
{
if ((reg_mask & (1 << regno)) != 0)
{
offset -= UNITS_PER_WORD;
stack_offset -= UNITS_PER_WORD;
mt_emit_save_restore
(direction, gen_rtx_REG (SImode, regno),
gen_rtx_MEM (SImode,
gen_rtx_PLUS (SImode, base_reg, GEN_INT (offset))),
stack_offset);
}
}
}
/* Return true if FUNC is a function with the 'interrupt' attribute. */
static bool
mt_interrupt_function_p (tree func)
{
tree a;
if (TREE_CODE (func) != FUNCTION_DECL)
return false;
a = lookup_attribute ("interrupt", DECL_ATTRIBUTES (func));
return a != NULL_TREE;
}
/* Generate prologue code. */
void
mt_expand_prologue (void)
{
rtx size_rtx, insn;
unsigned int frame_size;
if (mt_interrupt_function_p (current_function_decl))
{
interrupt_handler = 1;
if (cfun->machine)
cfun->machine->interrupt_handler = 1;
}
mt_compute_frame_size (get_frame_size ());
if (TARGET_DEBUG_STACK)
mt_debug_stack (&current_frame_info);
/* Compute size of stack adjustment. */
frame_size = current_frame_info.total_size;
/* If offset doesn't fit in a 15-bit signed integer,
uses a scratch registers to get a smaller offset. */
if (CONST_OK_FOR_LETTER_P(frame_size, 'O'))
size_rtx = GEN_INT (frame_size);
else
{
/* We do not have any scratch registers. */
gcc_assert (!interrupt_handler);
size_rtx = gen_rtx_REG (SImode, GPR_R9);
insn = emit_move_insn (size_rtx, GEN_INT (frame_size & 0xffff0000));
insn = emit_insn (gen_iorsi3 (size_rtx, size_rtx,
GEN_INT (frame_size & 0x0000ffff)));
}
/* Allocate stack for this frame. */
/* Make stack adjustment and use scratch register if constant too
large to fit as immediate. */
if (frame_size)
{
insn = emit_insn (gen_subsi3 (stack_pointer_rtx,
stack_pointer_rtx,
size_rtx));
RTX_FRAME_RELATED_P (insn) = 1;
REG_NOTES (insn)
= gen_rtx_EXPR_LIST (REG_FRAME_RELATED_EXPR,
gen_rtx_SET (VOIDmode,
stack_pointer_rtx,
gen_rtx_MINUS (SImode,
stack_pointer_rtx,
GEN_INT (frame_size))),
REG_NOTES (insn));
}
/* Set R9 to point to old sp if required for access to register save
area. */
if ( current_frame_info.reg_size != 0
&& !CONST_OK_FOR_LETTER_P (frame_size, 'O'))
emit_insn (gen_addsi3 (size_rtx, size_rtx, stack_pointer_rtx));
/* Save the frame pointer. */
mt_emit_save_fp (FROM_PROCESSOR_TO_MEM, current_frame_info);
/* Now put the frame pointer into the frame pointer register. */
if (frame_pointer_needed)
{
insn = emit_move_insn (frame_pointer_rtx, stack_pointer_rtx);
RTX_FRAME_RELATED_P (insn) = 1;
}
/* Save the registers. */
mt_emit_save_regs (FROM_PROCESSOR_TO_MEM, current_frame_info);
/* If we are profiling, make sure no instructions are scheduled before
the call to mcount. */
if (profile_flag)
emit_insn (gen_blockage ());
}
/* Implement EPILOGUE_USES. */
int
mt_epilogue_uses (int regno)
{
if (cfun->machine && cfun->machine->interrupt_handler && reload_completed)
return 1;
return regno == GPR_LINK;
}
/* Generate epilogue. EH_MODE is NORMAL_EPILOGUE when generating a
function epilogue, or EH_EPILOGUE when generating an EH
epilogue. */
void
mt_expand_epilogue (enum epilogue_type eh_mode)
{
rtx size_rtx, insn;
unsigned frame_size;
mt_compute_frame_size (get_frame_size ());
if (TARGET_DEBUG_STACK)
mt_debug_stack (& current_frame_info);
/* Compute size of stack adjustment. */
frame_size = current_frame_info.total_size;
/* If offset doesn't fit in a 15-bit signed integer,
uses a scratch registers to get a smaller offset. */
if (CONST_OK_FOR_LETTER_P(frame_size, 'O'))
size_rtx = GEN_INT (frame_size);
else
{
/* We do not have any scratch registers. */
gcc_assert (!interrupt_handler);
size_rtx = gen_rtx_REG (SImode, GPR_R9);
insn = emit_move_insn (size_rtx, GEN_INT (frame_size & 0xffff0000));
insn = emit_insn (gen_iorsi3 (size_rtx, size_rtx,
GEN_INT (frame_size & 0x0000ffff)));
/* Set R9 to point to old sp if required for access to register
save area. */
emit_insn (gen_addsi3 (size_rtx, size_rtx, stack_pointer_rtx));
}
/* Restore sp if there was some possible change to it. */
if (frame_pointer_needed)
insn = emit_move_insn (stack_pointer_rtx, frame_pointer_rtx);
/* Restore the registers. */
mt_emit_save_fp (FROM_MEM_TO_PROCESSOR, current_frame_info);
mt_emit_save_regs (FROM_MEM_TO_PROCESSOR, current_frame_info);
/* Make stack adjustment and use scratch register if constant too
large to fit as immediate. */
if (frame_size)
{
if (CONST_OK_FOR_LETTER_P(frame_size, 'O'))
/* Can handle this with simple add. */
insn = emit_insn (gen_addsi3 (stack_pointer_rtx,
stack_pointer_rtx,
size_rtx));
else
/* Scratch reg R9 has the old sp value. */
insn = emit_move_insn (stack_pointer_rtx,
gen_rtx_REG (SImode, GPR_R9));
REG_NOTES (insn)
= gen_rtx_EXPR_LIST (REG_FRAME_RELATED_EXPR,
gen_rtx_SET (VOIDmode,
stack_pointer_rtx,
gen_rtx_PLUS (SImode,
stack_pointer_rtx,
GEN_INT (frame_size))),
REG_NOTES (insn));
}
if (cfun->machine && cfun->machine->eh_stack_adjust != NULL_RTX)
/* Perform the additional bump for __throw. */
emit_insn (gen_addsi3 (stack_pointer_rtx,
stack_pointer_rtx,
cfun->machine->eh_stack_adjust));
/* Generate the appropriate return. */
if (eh_mode == EH_EPILOGUE)
{
emit_jump_insn (gen_eh_return_internal ());
emit_barrier ();
}
else if (interrupt_handler)
emit_jump_insn (gen_return_interrupt_internal ());
else
emit_jump_insn (gen_return_internal ());
/* Reset state info for each function. */
interrupt_handler = 0;
current_frame_info = zero_frame_info;
if (cfun->machine)
cfun->machine->eh_stack_adjust = NULL_RTX;
}
/* Generate code for the "eh_return" pattern. */
void
mt_expand_eh_return (rtx * operands)
{
if (GET_CODE (operands[0]) != REG
|| REGNO (operands[0]) != EH_RETURN_STACKADJ_REGNO)
{
rtx sp = EH_RETURN_STACKADJ_RTX;
emit_move_insn (sp, operands[0]);
operands[0] = sp;
}
emit_insn (gen_eh_epilogue (operands[0]));
}
/* Generate code for the "eh_epilogue" pattern. */
void
mt_emit_eh_epilogue (rtx * operands ATTRIBUTE_UNUSED)
{
cfun->machine->eh_stack_adjust = EH_RETURN_STACKADJ_RTX; /* operands[0]; */
mt_expand_epilogue (EH_EPILOGUE);
}
/* Handle an "interrupt" attribute. */
static tree
mt_handle_interrupt_attribute (tree * node,
tree name,
tree args ATTRIBUTE_UNUSED,
int flags ATTRIBUTE_UNUSED,
bool * no_add_attrs)
{
if (TREE_CODE (*node) != FUNCTION_DECL)
{
warning (OPT_Wattributes,
"%qs attribute only applies to functions",
IDENTIFIER_POINTER (name));
*no_add_attrs = true;
}
return NULL_TREE;
}
/* Table of machine attributes. */
const struct attribute_spec mt_attribute_table[] =
{
/* name, min, max, decl?, type?, func?, handler */
{ "interrupt", 0, 0, false, false, false, mt_handle_interrupt_attribute },
{ NULL, 0, 0, false, false, false, NULL }
};
/* Implement INITIAL_ELIMINATION_OFFSET. */
int
mt_initial_elimination_offset (int from, int to)
{
mt_compute_frame_size (get_frame_size ());
if (from == FRAME_POINTER_REGNUM && to == STACK_POINTER_REGNUM)
return 0;
else if (from == ARG_POINTER_REGNUM && to == STACK_POINTER_REGNUM)
return current_frame_info.total_size;
else if (from == ARG_POINTER_REGNUM && to == FRAME_POINTER_REGNUM)
return current_frame_info.total_size;
else
gcc_unreachable ();
}
/* Generate a compare for CODE. Return a brand-new rtx that
represents the result of the compare. */
static rtx
mt_generate_compare (enum rtx_code code, rtx op0, rtx op1)
{
rtx scratch0, scratch1, const_scratch;
switch (code)
{
case GTU:
case LTU:
case GEU:
case LEU:
/* Need to adjust ranges for faking unsigned compares. */
scratch0 = gen_reg_rtx (SImode);
scratch1 = gen_reg_rtx (SImode);
const_scratch = force_reg (SImode, GEN_INT(MT_MIN_INT));
emit_insn (gen_addsi3 (scratch0, const_scratch, op0));
emit_insn (gen_addsi3 (scratch1, const_scratch, op1));
break;
default:
scratch0 = op0;
scratch1 = op1;
break;
}
/* Adjust compare operator to fake unsigned compares. */
switch (code)
{
case GTU:
code = GT; break;
case LTU:
code = LT; break;
case GEU:
code = GE; break;
case LEU:
code = LE; break;
default:
/* do nothing */
break;
}
/* Generate the actual compare. */
return gen_rtx_fmt_ee (code, VOIDmode, scratch0, scratch1);
}
/* Emit a branch of kind CODE to location LOC. */
void
mt_emit_cbranch (enum rtx_code code, rtx loc, rtx op0, rtx op1)
{
rtx condition_rtx, loc_ref;
if (! reg_or_0_operand (op0, SImode))
op0 = copy_to_mode_reg (SImode, op0);
if (! reg_or_0_operand (op1, SImode))
op1 = copy_to_mode_reg (SImode, op1);
condition_rtx = mt_generate_compare (code, op0, op1);
loc_ref = gen_rtx_LABEL_REF (VOIDmode, loc);
emit_jump_insn (gen_rtx_SET (VOIDmode, pc_rtx,
gen_rtx_IF_THEN_ELSE (VOIDmode, condition_rtx,
loc_ref, pc_rtx)));
}
/* Subfunction of the following function. Update the flags of any MEM
found in part of X. */
static void
mt_set_memflags_1 (rtx x, int in_struct_p, int volatile_p)
{
int i;
switch (GET_CODE (x))
{
case SEQUENCE:
case PARALLEL:
for (i = XVECLEN (x, 0) - 1; i >= 0; i--)
mt_set_memflags_1 (XVECEXP (x, 0, i), in_struct_p, volatile_p);
break;
case INSN:
mt_set_memflags_1 (PATTERN (x), in_struct_p, volatile_p);
break;
case SET:
mt_set_memflags_1 (SET_DEST (x), in_struct_p, volatile_p);
mt_set_memflags_1 (SET_SRC (x), in_struct_p, volatile_p);
break;
case MEM:
MEM_IN_STRUCT_P (x) = in_struct_p;
MEM_VOLATILE_P (x) = volatile_p;
/* Sadly, we cannot use alias sets because the extra aliasing
produced by the AND interferes. Given that two-byte quantities
are the only thing we would be able to differentiate anyway,
there does not seem to be any point in convoluting the early
out of the alias check. */
/* set_mem_alias_set (x, alias_set); */
break;
default:
break;
}
}
/* Look for any MEMs in the current sequence of insns and set the
in-struct, unchanging, and volatile flags from the flags in REF.
If REF is not a MEM, don't do anything. */
void
mt_set_memflags (rtx ref)
{
rtx insn;
int in_struct_p, volatile_p;
if (GET_CODE (ref) != MEM)
return;
in_struct_p = MEM_IN_STRUCT_P (ref);
volatile_p = MEM_VOLATILE_P (ref);
/* This is only called from mt.md, after having had something
generated from one of the insn patterns. So if everything is
zero, the pattern is already up-to-date. */
if (! in_struct_p && ! volatile_p)
return;
for (insn = get_insns (); insn; insn = NEXT_INSN (insn))
mt_set_memflags_1 (insn, in_struct_p, volatile_p);
}
/* Implement SECONDARY_RELOAD_CLASS. */
enum reg_class
mt_secondary_reload_class (enum reg_class class ATTRIBUTE_UNUSED,
enum machine_mode mode,
rtx x)
{
if ((mode == QImode && (!TARGET_BYTE_ACCESS)) || mode == HImode)
{
if (GET_CODE (x) == MEM
|| (GET_CODE (x) == REG && true_regnum (x) == -1)
|| (GET_CODE (x) == SUBREG
&& (GET_CODE (SUBREG_REG (x)) == MEM
|| (GET_CODE (SUBREG_REG (x)) == REG
&& true_regnum (SUBREG_REG (x)) == -1))))
return GENERAL_REGS;
}
return NO_REGS;
}
/* Handle FUNCTION_VALUE, FUNCTION_OUTGOING_VALUE, and LIBCALL_VALUE
macros. */
rtx
mt_function_value (tree valtype, enum machine_mode mode, tree func_decl ATTRIBUTE_UNUSED)
{
if ((mode) == DImode || (mode) == DFmode)
return gen_rtx_MEM (mode, gen_rtx_REG (mode, RETURN_VALUE_REGNUM));
if (valtype)
mode = TYPE_MODE (valtype);
return gen_rtx_REG (mode, RETURN_VALUE_REGNUM);
}
/* Split a move into two smaller pieces.
MODE indicates the reduced mode. OPERANDS[0] is the original destination
OPERANDS[1] is the original src. The new destinations are
OPERANDS[2] and OPERANDS[4], while the new sources are OPERANDS[3]
and OPERANDS[5]. */
void
mt_split_words (enum machine_mode nmode,
enum machine_mode omode,
rtx *operands)
{
rtx dl,dh; /* src/dest pieces. */
rtx sl,sh;
int move_high_first = 0; /* Assume no overlap. */
switch (GET_CODE (operands[0])) /* Dest. */
{
case SUBREG:
case REG:
if ((GET_CODE (operands[1]) == REG
|| GET_CODE (operands[1]) == SUBREG)
&& true_regnum (operands[0]) <= true_regnum (operands[1]))
move_high_first = 1;
if (GET_CODE (operands[0]) == SUBREG)
{
dl = gen_rtx_SUBREG (nmode, SUBREG_REG (operands[0]),
SUBREG_BYTE (operands[0]) + GET_MODE_SIZE (nmode));
dh = gen_rtx_SUBREG (nmode, SUBREG_REG (operands[0]), SUBREG_BYTE (operands[0]));
}
else if (GET_CODE (operands[0]) == REG && ! IS_PSEUDO_P (operands[0]))
{
int r = REGNO (operands[0]);
dh = gen_rtx_REG (nmode, r);
dl = gen_rtx_REG (nmode, r + HARD_REGNO_NREGS (r, nmode));
}
else
{
dh = gen_rtx_SUBREG (nmode, operands[0], 0);
dl = gen_rtx_SUBREG (nmode, operands[0], GET_MODE_SIZE (nmode));
}
break;
case MEM:
switch (GET_CODE (XEXP (operands[0], 0)))
{
case POST_INC:
case POST_DEC:
gcc_unreachable ();
default:
dl = operand_subword (operands[0],
GET_MODE_SIZE (nmode)/UNITS_PER_WORD,
0, omode);
dh = operand_subword (operands[0], 0, 0, omode);
}
break;
default:
gcc_unreachable ();
}
switch (GET_CODE (operands[1]))
{
case REG:
if (! IS_PSEUDO_P (operands[1]))
{
int r = REGNO (operands[1]);
sh = gen_rtx_REG (nmode, r);
sl = gen_rtx_REG (nmode, r + HARD_REGNO_NREGS (r, nmode));
}
else
{
sh = gen_rtx_SUBREG (nmode, operands[1], 0);
sl = gen_rtx_SUBREG (nmode, operands[1], GET_MODE_SIZE (nmode));
}
break;
case CONST_DOUBLE:
if (operands[1] == const0_rtx)
sh = sl = const0_rtx;
else
split_double (operands[1], & sh, & sl);
break;
case CONST_INT:
if (operands[1] == const0_rtx)
sh = sl = const0_rtx;
else
{
int vl, vh;
switch (nmode)
{
default:
gcc_unreachable ();
}
sl = GEN_INT (vl);
sh = GEN_INT (vh);
}
break;
case SUBREG:
sl = gen_rtx_SUBREG (nmode,
SUBREG_REG (operands[1]),
SUBREG_BYTE (operands[1]) + GET_MODE_SIZE (nmode));
sh = gen_rtx_SUBREG (nmode,
SUBREG_REG (operands[1]),
SUBREG_BYTE (operands[1]));
break;
case MEM:
switch (GET_CODE (XEXP (operands[1], 0)))
{
case POST_DEC:
case POST_INC:
gcc_unreachable ();
break;
default:
sl = operand_subword (operands[1],
GET_MODE_SIZE (nmode)/UNITS_PER_WORD,
0, omode);
sh = operand_subword (operands[1], 0, 0, omode);
/* Check if the DF load is going to clobber the register
used for the address, and if so make sure that is going
to be the second move. */
if (GET_CODE (dl) == REG
&& true_regnum (dl)
== true_regnum (XEXP (XEXP (sl, 0 ), 0)))
move_high_first = 1;
}
break;
default:
gcc_unreachable ();
}
if (move_high_first)
{
operands[2] = dh;
operands[3] = sh;
operands[4] = dl;
operands[5] = sl;
}
else
{
operands[2] = dl;
operands[3] = sl;
operands[4] = dh;
operands[5] = sh;
}
return;
}
/* Implement TARGET_MUST_PASS_IN_STACK hook. */
static bool
mt_pass_in_stack (enum machine_mode mode ATTRIBUTE_UNUSED, tree type)
{
return (((type) != 0
&& (TREE_CODE (TYPE_SIZE (type)) != INTEGER_CST
|| TREE_ADDRESSABLE (type))));
}
/* Increment the counter for the number of loop instructions in the
current function. */
void mt_add_loop (void)
{
cfun->machine->has_loops++;
}
/* Maximum loop nesting depth. */
#define MAX_LOOP_DEPTH 4
/* Maximum size of a loop (allows some headroom for delayed branch slot
filling. */
#define MAX_LOOP_LENGTH (200 * 4)
/* We need to keep a vector of loops */
typedef struct loop_info *loop_info;
DEF_VEC_P (loop_info);
DEF_VEC_ALLOC_P (loop_info,heap);
/* Information about a loop we have found (or are in the process of
finding). */
struct loop_info GTY (())
{
/* loop number, for dumps */
int loop_no;
/* Predecessor block of the loop. This is the one that falls into
the loop and contains the initialization instruction. */
basic_block predecessor;
/* First block in the loop. This is the one branched to by the dbnz
insn. */
basic_block head;
/* Last block in the loop (the one with the dbnz insn */
basic_block tail;
/* The successor block of the loop. This is the one the dbnz insn
falls into. */
basic_block successor;
/* The dbnz insn. */
rtx dbnz;
/* The initialization insn. */
rtx init;
/* The new initialization instruction. */
rtx loop_init;
/* The new ending instruction. */
rtx loop_end;
/* The new label placed at the end of the loop. */
rtx end_label;
/* The nesting depth of the loop. Set to -1 for a bad loop. */
int depth;
/* The length of the loop. */
int length;
/* Next loop in the graph. */
struct loop_info *next;
/* Vector of blocks only within the loop, (excluding those within
inner loops). */
VEC (basic_block,heap) *blocks;
/* Vector of inner loops within this loop */
VEC (loop_info,heap) *loops;
};
/* Information used during loop detection. */
typedef struct loop_work GTY(())
{
/* Basic block to be scanned. */
basic_block block;
/* Loop it will be within. */
loop_info loop;
} loop_work;
/* Work list. */
DEF_VEC_O (loop_work);
DEF_VEC_ALLOC_O (loop_work,heap);
/* Determine the nesting and length of LOOP. Return false if the loop
is bad. */
static bool
mt_loop_nesting (loop_info loop)
{
loop_info inner;
unsigned ix;
int inner_depth = 0;
if (!loop->depth)
{
/* Make sure we only have one entry point. */
if (EDGE_COUNT (loop->head->preds) == 2)
{
loop->predecessor = EDGE_PRED (loop->head, 0)->src;
if (loop->predecessor == loop->tail)
/* We wanted the other predecessor. */
loop->predecessor = EDGE_PRED (loop->head, 1)->src;
/* We can only place a loop insn on a fall through edge of a
single exit block. */
if (EDGE_COUNT (loop->predecessor->succs) != 1
|| !(EDGE_SUCC (loop->predecessor, 0)->flags & EDGE_FALLTHRU))
loop->predecessor = NULL;
}
/* Mark this loop as bad for now. */
loop->depth = -1;
if (loop->predecessor)
{
for (ix = 0; VEC_iterate (loop_info, loop->loops, ix++, inner);)
{
if (!inner->depth)
mt_loop_nesting (inner);
if (inner->depth < 0)
{
inner_depth = -1;
break;
}
if (inner_depth < inner->depth)
inner_depth = inner->depth;
loop->length += inner->length;
}
/* Set the proper loop depth, if it was good. */
if (inner_depth >= 0)
loop->depth = inner_depth + 1;
}
}
return (loop->depth > 0
&& loop->predecessor
&& loop->depth < MAX_LOOP_DEPTH
&& loop->length < MAX_LOOP_LENGTH);
}
/* Determine the length of block BB. */
static int
mt_block_length (basic_block bb)
{
int length = 0;
rtx insn;
for (insn = BB_HEAD (bb);
insn != NEXT_INSN (BB_END (bb));
insn = NEXT_INSN (insn))
{
if (!INSN_P (insn))
continue;
if (CALL_P (insn))
{
/* Calls are not allowed in loops. */
length = MAX_LOOP_LENGTH + 1;
break;
}
length += get_attr_length (insn);
}
return length;
}
/* Scan the blocks of LOOP (and its inferiors) looking for uses of
REG. Return true, if we find any. Don't count the loop's dbnz
insn if it matches DBNZ. */
static bool
mt_scan_loop (loop_info loop, rtx reg, rtx dbnz)
{
unsigned ix;
loop_info inner;
basic_block bb;
for (ix = 0; VEC_iterate (basic_block, loop->blocks, ix, bb); ix++)
{
rtx insn;
for (insn = BB_HEAD (bb);
insn != NEXT_INSN (BB_END (bb));
insn = NEXT_INSN (insn))
{
if (!INSN_P (insn))
continue;
if (insn == dbnz)
continue;
if (reg_mentioned_p (reg, PATTERN (insn)))
return true;
}
}
for (ix = 0; VEC_iterate (loop_info, loop->loops, ix, inner); ix++)
if (mt_scan_loop (inner, reg, NULL_RTX))
return true;
return false;
}
/* MS2 has a loop instruction which needs to be placed just before the
loop. It indicates the end of the loop and specifies the number of
loop iterations. It can be nested with an automatically maintained
stack of counter and end address registers. It's an ideal
candidate for doloop. Unfortunately, gcc presumes that loops
always end with an explicit instruction, and the doloop_begin
instruction is not a flow control instruction so it can be
scheduled earlier than just before the start of the loop. To make
matters worse, the optimization pipeline can duplicate loop exit
and entrance blocks and fails to track abnormally exiting loops.
Thus we cannot simply use doloop.
What we do is emit a dbnz pattern for the doloop optimization, and
let that be optimized as normal. Then in machine dependent reorg
we have to repeat the loop searching algorithm. We use the
flow graph to find closed loops ending in a dbnz insn. We then try
and convert it to use the loop instruction. The conditions are,
* the loop has no abnormal exits, duplicated end conditions or
duplicated entrance blocks
* the loop counter register is only used in the dbnz instruction
within the loop
* we can find the instruction setting the initial value of the loop
counter
* the loop is not executed more than 65535 times. (This might be
changed to 2^32-1, and would therefore allow variable initializers.)
* the loop is not nested more than 4 deep 5) there are no
subroutine calls in the loop. */
static void
mt_reorg_loops (FILE *dump_file)
{
basic_block bb;
loop_info loops = NULL;
loop_info loop;
int nloops = 0;
unsigned dwork = 0;
VEC (loop_work,heap) *works = VEC_alloc (loop_work,heap,20);
loop_work *work;
edge e;
edge_iterator ei;
bool replaced = false;
/* Find all the possible loop tails. This means searching for every
dbnz instruction. For each one found, create a loop_info
structure and add the head block to the work list. */
FOR_EACH_BB (bb)
{
rtx tail = BB_END (bb);
while (GET_CODE (tail) == NOTE)
tail = PREV_INSN (tail);
bb->aux = NULL;
if (recog_memoized (tail) == CODE_FOR_decrement_and_branch_until_zero)
{
/* A possible loop end */
loop = XNEW (struct loop_info);
loop->next = loops;
loops = loop;
loop->tail = bb;
loop->head = BRANCH_EDGE (bb)->dest;
loop->successor = FALLTHRU_EDGE (bb)->dest;
loop->predecessor = NULL;
loop->dbnz = tail;
loop->depth = 0;
loop->length = mt_block_length (bb);
loop->blocks = VEC_alloc (basic_block, heap, 20);
VEC_quick_push (basic_block, loop->blocks, bb);
loop->loops = NULL;
loop->loop_no = nloops++;
loop->init = loop->end_label = NULL_RTX;
loop->loop_init = loop->loop_end = NULL_RTX;
work = VEC_safe_push (loop_work, heap, works, NULL);
work->block = loop->head;
work->loop = loop;
bb->aux = loop;
if (dump_file)
{
fprintf (dump_file, ";; potential loop %d ending at\n",
loop->loop_no);
print_rtl_single (dump_file, tail);
}
}
}
/* Now find all the closed loops.
until work list empty,
if block's auxptr is set
if != loop slot
if block's loop's start != block
mark loop as bad
else
append block's loop's fallthrough block to worklist
increment this loop's depth
else if block is exit block
mark loop as bad
else
set auxptr
for each target of block
add to worklist */
while (VEC_iterate (loop_work, works, dwork++, work))
{
loop = work->loop;
bb = work->block;
if (bb == EXIT_BLOCK_PTR)
/* We've reached the exit block. The loop must be bad. */
loop->depth = -1;
else if (!bb->aux)
{
/* We've not seen this block before. Add it to the loop's
list and then add each successor to the work list. */
bb->aux = loop;
loop->length += mt_block_length (bb);
VEC_safe_push (basic_block, heap, loop->blocks, bb);
FOR_EACH_EDGE (e, ei, bb->succs)
{
if (!VEC_space (loop_work, works, 1))
{
if (dwork)
{
VEC_block_remove (loop_work, works, 0, dwork);
dwork = 0;
}
else
VEC_reserve (loop_work, heap, works, 1);
}
work = VEC_quick_push (loop_work, works, NULL);
work->block = EDGE_SUCC (bb, ei.index)->dest;
work->loop = loop;
}
}
else if (bb->aux != loop)
{
/* We've seen this block in a different loop. If it's not
the other loop's head, then this loop must be bad.
Otherwise, the other loop might be a nested loop, so
continue from that loop's successor. */
loop_info other = bb->aux;
if (other->head != bb)
loop->depth = -1;
else
{
VEC_safe_push (loop_info, heap, loop->loops, other);
work = VEC_safe_push (loop_work, heap, works, NULL);
work->loop = loop;
work->block = other->successor;
}
}
}
VEC_free (loop_work, heap, works);
/* Now optimize the loops. */
for (loop = loops; loop; loop = loop->next)
{
rtx iter_reg, insn, init_insn;
rtx init_val, loop_end, loop_init, end_label, head_label;
if (!mt_loop_nesting (loop))
{
if (dump_file)
fprintf (dump_file, ";; loop %d is bad\n", loop->loop_no);
continue;
}
/* Get the loop iteration register. */
iter_reg = SET_DEST (XVECEXP (PATTERN (loop->dbnz), 0, 1));
if (!REG_P (iter_reg))
{
/* Spilled */
if (dump_file)
fprintf (dump_file, ";; loop %d has spilled iteration count\n",
loop->loop_no);
continue;
}
/* Look for the initializing insn */
init_insn = NULL_RTX;
for (insn = BB_END (loop->predecessor);
insn != PREV_INSN (BB_HEAD (loop->predecessor));
insn = PREV_INSN (insn))
{
if (!INSN_P (insn))
continue;
if (reg_mentioned_p (iter_reg, PATTERN (insn)))
{
rtx set = single_set (insn);
if (set && rtx_equal_p (iter_reg, SET_DEST (set)))
init_insn = insn;
break;
}
}
if (!init_insn)
{
if (dump_file)
fprintf (dump_file, ";; loop %d has no initializer\n",
loop->loop_no);
continue;
}
if (dump_file)
{
fprintf (dump_file, ";; loop %d initialized by\n",
loop->loop_no);
print_rtl_single (dump_file, init_insn);
}
init_val = PATTERN (init_insn);
if (GET_CODE (init_val) == SET)
init_val = SET_SRC (init_val);
if (GET_CODE (init_val) != CONST_INT || INTVAL (init_val) >= 65535)
{
if (dump_file)
fprintf (dump_file, ";; loop %d has complex initializer\n",
loop->loop_no);
continue;
}
/* Scan all the blocks to make sure they don't use iter_reg. */
if (mt_scan_loop (loop, iter_reg, loop->dbnz))
{
if (dump_file)
fprintf (dump_file, ";; loop %d uses iterator\n",
loop->loop_no);
continue;
}
/* The loop is good for replacement. */
/* loop is 1 based, dbnz is zero based. */
init_val = GEN_INT (INTVAL (init_val) + 1);
iter_reg = gen_rtx_REG (SImode, LOOP_FIRST + loop->depth - 1);
end_label = gen_label_rtx ();
head_label = XEXP (SET_SRC (XVECEXP (PATTERN (loop->dbnz), 0, 0)), 1);
loop_end = gen_loop_end (iter_reg, head_label);
loop_init = gen_loop_init (iter_reg, init_val, end_label);
loop->init = init_insn;
loop->end_label = end_label;
loop->loop_init = loop_init;
loop->loop_end = loop_end;
replaced = true;
if (dump_file)
{
fprintf (dump_file, ";; replacing loop %d initializer with\n",
loop->loop_no);
print_rtl_single (dump_file, loop->loop_init);
fprintf (dump_file, ";; replacing loop %d terminator with\n",
loop->loop_no);
print_rtl_single (dump_file, loop->loop_end);
}
}
/* Now apply the optimizations. Do it this way so we don't mess up
the flow graph half way through. */
for (loop = loops; loop; loop = loop->next)
if (loop->loop_init)
{
emit_jump_insn_after (loop->loop_init, BB_END (loop->predecessor));
delete_insn (loop->init);
emit_label_before (loop->end_label, loop->dbnz);
emit_jump_insn_before (loop->loop_end, loop->dbnz);
delete_insn (loop->dbnz);
}
/* Free up the loop structures */
while (loops)
{
loop = loops;
loops = loop->next;
VEC_free (loop_info, heap, loop->loops);
VEC_free (basic_block, heap, loop->blocks);
XDELETE (loop);
}
if (replaced && dump_file)
{
fprintf (dump_file, ";; Replaced loops\n");
print_rtl (dump_file, get_insns ());
}
}
/* Structures to hold branch information during reorg. */
typedef struct branch_info
{
rtx insn; /* The branch insn. */
struct branch_info *next;
} branch_info;
typedef struct label_info
{
rtx label; /* The label. */
branch_info *branches; /* branches to this label. */
struct label_info *next;
} label_info;
/* Chain of labels found in current function, used during reorg. */
static label_info *mt_labels;
/* If *X is a label, add INSN to the list of branches for that
label. */
static int
mt_add_branches (rtx *x, void *insn)
{
if (GET_CODE (*x) == LABEL_REF)
{
branch_info *branch = xmalloc (sizeof (*branch));
rtx label = XEXP (*x, 0);
label_info *info;
for (info = mt_labels; info; info = info->next)
if (info->label == label)
break;
if (!info)
{
info = xmalloc (sizeof (*info));
info->next = mt_labels;
mt_labels = info;
info->label = label;
info->branches = NULL;
}
branch->next = info->branches;
info->branches = branch;
branch->insn = insn;
}
return 0;
}
/* If BRANCH has a filled delay slot, check if INSN is dependent upon
it. If so, undo the delay slot fill. Returns the next insn, if
we patch out the branch. Returns the branch insn, if we cannot
patch out the branch (due to anti-dependency in the delay slot).
In that case, the caller must insert nops at the branch target. */
static rtx
mt_check_delay_slot (rtx branch, rtx insn)
{
rtx slot;
rtx tmp;
rtx p;
rtx jmp;
gcc_assert (GET_CODE (PATTERN (branch)) == SEQUENCE);
if (INSN_DELETED_P (branch))
return NULL_RTX;
slot = XVECEXP (PATTERN (branch), 0, 1);
tmp = PATTERN (insn);
note_stores (PATTERN (slot), insn_dependent_p_1, &tmp);
if (tmp)
/* Not dependent. */
return NULL_RTX;
/* Undo the delay slot. */
jmp = XVECEXP (PATTERN (branch), 0, 0);
tmp = PATTERN (jmp);
note_stores (PATTERN (slot), insn_dependent_p_1, &tmp);
if (!tmp)
/* Anti dependent. */
return branch;
p = PREV_INSN (branch);
NEXT_INSN (p) = slot;
PREV_INSN (slot) = p;
NEXT_INSN (slot) = jmp;
PREV_INSN (jmp) = slot;
NEXT_INSN (jmp) = branch;
PREV_INSN (branch) = jmp;
XVECEXP (PATTERN (branch), 0, 0) = NULL_RTX;
XVECEXP (PATTERN (branch), 0, 1) = NULL_RTX;
delete_insn (branch);
return jmp;
}
/* Insert nops to satisfy pipeline constraints. We only deal with ms2
constraints here. Earlier CPUs are dealt with by inserting nops with
final_prescan (but that can lead to inferior code, and is
impractical with ms2's JAL hazard).
ms2 dynamic constraints
1) a load and a following use must be separated by one insn
2) an insn and a following dependent call must be separated by two insns
only arith insns are placed in delay slots so #1 cannot happen with
a load in a delay slot. #2 can happen with an arith insn in the
delay slot. */
static void
mt_reorg_hazard (void)
{
rtx insn, next;
/* Find all the branches */
for (insn = get_insns ();
insn;
insn = NEXT_INSN (insn))
{
rtx jmp;
if (!INSN_P (insn))
continue;
jmp = PATTERN (insn);
if (GET_CODE (jmp) != SEQUENCE)
/* If it's not got a filled delay slot, then it can't
conflict. */
continue;
jmp = XVECEXP (jmp, 0, 0);
if (recog_memoized (jmp) == CODE_FOR_tablejump)
for (jmp = XEXP (XEXP (XVECEXP (PATTERN (jmp), 0, 1), 0), 0);
!JUMP_TABLE_DATA_P (jmp);
jmp = NEXT_INSN (jmp))
continue;
for_each_rtx (&PATTERN (jmp), mt_add_branches, insn);
}
/* Now scan for dependencies. */
for (insn = get_insns ();
insn && !INSN_P (insn);
insn = NEXT_INSN (insn))
continue;
for (;
insn;
insn = next)
{
rtx jmp, tmp;
enum attr_type attr;
gcc_assert (INSN_P (insn) && !INSN_DELETED_P (insn));
for (next = NEXT_INSN (insn);
next;
next = NEXT_INSN (next))
{
if (!INSN_P (next))
continue;
if (GET_CODE (PATTERN (next)) != USE)
break;
}
jmp = insn;
if (GET_CODE (PATTERN (insn)) == SEQUENCE)
jmp = XVECEXP (PATTERN (insn), 0, 0);
attr = recog_memoized (jmp) >= 0 ? get_attr_type (jmp) : TYPE_UNKNOWN;
if (next && attr == TYPE_LOAD)
{
/* A load. See if NEXT is dependent, and if so insert a
nop. */
tmp = PATTERN (next);
if (GET_CODE (tmp) == SEQUENCE)
tmp = PATTERN (XVECEXP (tmp, 0, 0));
note_stores (PATTERN (insn), insn_dependent_p_1, &tmp);
if (!tmp)
emit_insn_after (gen_nop (), insn);
}
if (attr == TYPE_CALL)
{
/* A call. Make sure we're not dependent on either of the
previous two dynamic instructions. */
int nops = 0;
int count;
rtx prev = insn;
rtx rescan = NULL_RTX;
for (count = 2; count && !nops;)
{
int type;
prev = PREV_INSN (prev);
if (!prev)
{
/* If we reach the start of the function, we must
presume the caller set the address in the delay
slot of the call instruction. */
nops = count;
break;
}
if (BARRIER_P (prev))
break;
if (LABEL_P (prev))
{
/* Look at branches to this label. */
label_info *label;
branch_info *branch;
for (label = mt_labels;
label;
label = label->next)
if (label->label == prev)
{
for (branch = label->branches;
branch;
branch = branch->next)
{
tmp = mt_check_delay_slot (branch->insn, jmp);
if (tmp == branch->insn)
{
nops = count;
break;
}
if (tmp && branch->insn == next)
rescan = tmp;
}
break;
}
continue;
}
if (!INSN_P (prev) || GET_CODE (PATTERN (prev)) == USE)
continue;
if (GET_CODE (PATTERN (prev)) == SEQUENCE)
{
/* Look at the delay slot. */
tmp = mt_check_delay_slot (prev, jmp);
if (tmp == prev)
nops = count;
break;
}
type = (INSN_CODE (prev) >= 0 ? get_attr_type (prev)
: TYPE_COMPLEX);
if (type == TYPE_CALL || type == TYPE_BRANCH)
break;
if (type == TYPE_LOAD
|| type == TYPE_ARITH
|| type == TYPE_COMPLEX)
{
tmp = PATTERN (jmp);
note_stores (PATTERN (prev), insn_dependent_p_1, &tmp);
if (!tmp)
{
nops = count;
break;
}
}
if (INSN_CODE (prev) >= 0)
count--;
}
if (rescan)
for (next = NEXT_INSN (rescan);
next && !INSN_P (next);
next = NEXT_INSN (next))
continue;
while (nops--)
emit_insn_before (gen_nop (), insn);
}
}
/* Free the data structures. */
while (mt_labels)
{
label_info *label = mt_labels;
branch_info *branch, *next;
mt_labels = label->next;
for (branch = label->branches; branch; branch = next)
{
next = branch->next;
free (branch);
}
free (label);
}
}
/* Fixup the looping instructions, do delayed branch scheduling, fixup
scheduling hazards. */
static void
mt_machine_reorg (void)
{
if (cfun->machine->has_loops && TARGET_MS2)
mt_reorg_loops (dump_file);
if (mt_flag_delayed_branch)
dbr_schedule (get_insns ());
if (TARGET_MS2)
{
/* Force all instructions to be split into their final form. */
split_all_insns_noflow ();
mt_reorg_hazard ();
}
}
/* Initialize the GCC target structure. */
const struct attribute_spec mt_attribute_table[];
#undef TARGET_ATTRIBUTE_TABLE
#define TARGET_ATTRIBUTE_TABLE mt_attribute_table
#undef TARGET_STRUCT_VALUE_RTX
#define TARGET_STRUCT_VALUE_RTX mt_struct_value_rtx
#undef TARGET_PROMOTE_PROTOTYPES
#define TARGET_PROMOTE_PROTOTYPES hook_bool_tree_true
#undef TARGET_PASS_BY_REFERENCE
#define TARGET_PASS_BY_REFERENCE mt_pass_by_reference
#undef TARGET_MUST_PASS_IN_STACK
#define TARGET_MUST_PASS_IN_STACK mt_pass_in_stack
#undef TARGET_ARG_PARTIAL_BYTES
#define TARGET_ARG_PARTIAL_BYTES mt_arg_partial_bytes
#undef TARGET_SETUP_INCOMING_VARARGS
#define TARGET_SETUP_INCOMING_VARARGS mt_setup_incoming_varargs
#undef TARGET_MACHINE_DEPENDENT_REORG
#define TARGET_MACHINE_DEPENDENT_REORG mt_machine_reorg
struct gcc_target targetm = TARGET_INITIALIZER;
#include "gt-mt.h"