/* Subroutines for insn-output.c for Matsushita MN10300 series
|
/* Subroutines for insn-output.c for Matsushita MN10300 series
|
Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
|
Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
|
2007 Free Software Foundation, Inc.
|
2007 Free Software Foundation, Inc.
|
Contributed by Jeff Law (law@cygnus.com).
|
Contributed by Jeff Law (law@cygnus.com).
|
|
|
This file is part of GCC.
|
This file is part of GCC.
|
|
|
GCC is free software; you can redistribute it and/or modify
|
GCC is free software; you can redistribute it and/or modify
|
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
the Free Software Foundation; either version 3, or (at your option)
|
the Free Software Foundation; either version 3, or (at your option)
|
any later version.
|
any later version.
|
|
|
GCC is distributed in the hope that it will be useful,
|
GCC is distributed in the hope that it will be useful,
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
GNU General Public License for more details.
|
GNU General Public License for more details.
|
|
|
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
along with GCC; see the file COPYING3. If not see
|
along with GCC; see the file COPYING3. If not see
|
<http://www.gnu.org/licenses/>. */
|
<http://www.gnu.org/licenses/>. */
|
|
|
#include "config.h"
|
#include "config.h"
|
#include "system.h"
|
#include "system.h"
|
#include "coretypes.h"
|
#include "coretypes.h"
|
#include "tm.h"
|
#include "tm.h"
|
#include "rtl.h"
|
#include "rtl.h"
|
#include "tree.h"
|
#include "tree.h"
|
#include "regs.h"
|
#include "regs.h"
|
#include "hard-reg-set.h"
|
#include "hard-reg-set.h"
|
#include "real.h"
|
#include "real.h"
|
#include "insn-config.h"
|
#include "insn-config.h"
|
#include "conditions.h"
|
#include "conditions.h"
|
#include "output.h"
|
#include "output.h"
|
#include "insn-attr.h"
|
#include "insn-attr.h"
|
#include "flags.h"
|
#include "flags.h"
|
#include "recog.h"
|
#include "recog.h"
|
#include "expr.h"
|
#include "expr.h"
|
#include "optabs.h"
|
#include "optabs.h"
|
#include "function.h"
|
#include "function.h"
|
#include "obstack.h"
|
#include "obstack.h"
|
#include "toplev.h"
|
#include "toplev.h"
|
#include "tm_p.h"
|
#include "tm_p.h"
|
#include "target.h"
|
#include "target.h"
|
#include "target-def.h"
|
#include "target-def.h"
|
|
|
/* This is used by GOTaddr2picreg to uniquely identify
|
/* This is used by GOTaddr2picreg to uniquely identify
|
UNSPEC_INT_LABELs. */
|
UNSPEC_INT_LABELs. */
|
int mn10300_unspec_int_label_counter;
|
int mn10300_unspec_int_label_counter;
|
|
|
/* This is used in the am33_2.0-linux-gnu port, in which global symbol
|
/* This is used in the am33_2.0-linux-gnu port, in which global symbol
|
names are not prefixed by underscores, to tell whether to prefix a
|
names are not prefixed by underscores, to tell whether to prefix a
|
label with a plus sign or not, so that the assembler can tell
|
label with a plus sign or not, so that the assembler can tell
|
symbol names from register names. */
|
symbol names from register names. */
|
int mn10300_protect_label;
|
int mn10300_protect_label;
|
|
|
/* The selected processor. */
|
/* The selected processor. */
|
enum processor_type mn10300_processor = PROCESSOR_DEFAULT;
|
enum processor_type mn10300_processor = PROCESSOR_DEFAULT;
|
|
|
/* The size of the callee register save area. Right now we save everything
|
/* The size of the callee register save area. Right now we save everything
|
on entry since it costs us nothing in code size. It does cost us from a
|
on entry since it costs us nothing in code size. It does cost us from a
|
speed standpoint, so we want to optimize this sooner or later. */
|
speed standpoint, so we want to optimize this sooner or later. */
|
#define REG_SAVE_BYTES (4 * regs_ever_live[2] \
|
#define REG_SAVE_BYTES (4 * regs_ever_live[2] \
|
+ 4 * regs_ever_live[3] \
|
+ 4 * regs_ever_live[3] \
|
+ 4 * regs_ever_live[6] \
|
+ 4 * regs_ever_live[6] \
|
+ 4 * regs_ever_live[7] \
|
+ 4 * regs_ever_live[7] \
|
+ 16 * (regs_ever_live[14] || regs_ever_live[15] \
|
+ 16 * (regs_ever_live[14] || regs_ever_live[15] \
|
|| regs_ever_live[16] || regs_ever_live[17]))
|
|| regs_ever_live[16] || regs_ever_live[17]))
|
|
|
|
|
static bool mn10300_handle_option (size_t, const char *, int);
|
static bool mn10300_handle_option (size_t, const char *, int);
|
static int mn10300_address_cost_1 (rtx, int *);
|
static int mn10300_address_cost_1 (rtx, int *);
|
static int mn10300_address_cost (rtx);
|
static int mn10300_address_cost (rtx);
|
static bool mn10300_rtx_costs (rtx, int, int, int *);
|
static bool mn10300_rtx_costs (rtx, int, int, int *);
|
static void mn10300_file_start (void);
|
static void mn10300_file_start (void);
|
static bool mn10300_return_in_memory (tree, tree);
|
static bool mn10300_return_in_memory (tree, tree);
|
static rtx mn10300_builtin_saveregs (void);
|
static rtx mn10300_builtin_saveregs (void);
|
static bool mn10300_pass_by_reference (CUMULATIVE_ARGS *, enum machine_mode,
|
static bool mn10300_pass_by_reference (CUMULATIVE_ARGS *, enum machine_mode,
|
tree, bool);
|
tree, bool);
|
static int mn10300_arg_partial_bytes (CUMULATIVE_ARGS *, enum machine_mode,
|
static int mn10300_arg_partial_bytes (CUMULATIVE_ARGS *, enum machine_mode,
|
tree, bool);
|
tree, bool);
|
|
|
/* Initialize the GCC target structure. */
|
/* Initialize the GCC target structure. */
|
#undef TARGET_ASM_ALIGNED_HI_OP
|
#undef TARGET_ASM_ALIGNED_HI_OP
|
#define TARGET_ASM_ALIGNED_HI_OP "\t.hword\t"
|
#define TARGET_ASM_ALIGNED_HI_OP "\t.hword\t"
|
|
|
#undef TARGET_RTX_COSTS
|
#undef TARGET_RTX_COSTS
|
#define TARGET_RTX_COSTS mn10300_rtx_costs
|
#define TARGET_RTX_COSTS mn10300_rtx_costs
|
#undef TARGET_ADDRESS_COST
|
#undef TARGET_ADDRESS_COST
|
#define TARGET_ADDRESS_COST mn10300_address_cost
|
#define TARGET_ADDRESS_COST mn10300_address_cost
|
|
|
#undef TARGET_ASM_FILE_START
|
#undef TARGET_ASM_FILE_START
|
#define TARGET_ASM_FILE_START mn10300_file_start
|
#define TARGET_ASM_FILE_START mn10300_file_start
|
#undef TARGET_ASM_FILE_START_FILE_DIRECTIVE
|
#undef TARGET_ASM_FILE_START_FILE_DIRECTIVE
|
#define TARGET_ASM_FILE_START_FILE_DIRECTIVE true
|
#define TARGET_ASM_FILE_START_FILE_DIRECTIVE true
|
|
|
#undef TARGET_DEFAULT_TARGET_FLAGS
|
#undef TARGET_DEFAULT_TARGET_FLAGS
|
#define TARGET_DEFAULT_TARGET_FLAGS MASK_MULT_BUG | MASK_PTR_A0D0
|
#define TARGET_DEFAULT_TARGET_FLAGS MASK_MULT_BUG | MASK_PTR_A0D0
|
#undef TARGET_HANDLE_OPTION
|
#undef TARGET_HANDLE_OPTION
|
#define TARGET_HANDLE_OPTION mn10300_handle_option
|
#define TARGET_HANDLE_OPTION mn10300_handle_option
|
|
|
#undef TARGET_ENCODE_SECTION_INFO
|
#undef TARGET_ENCODE_SECTION_INFO
|
#define TARGET_ENCODE_SECTION_INFO mn10300_encode_section_info
|
#define TARGET_ENCODE_SECTION_INFO mn10300_encode_section_info
|
|
|
#undef TARGET_PROMOTE_PROTOTYPES
|
#undef TARGET_PROMOTE_PROTOTYPES
|
#define TARGET_PROMOTE_PROTOTYPES hook_bool_tree_true
|
#define TARGET_PROMOTE_PROTOTYPES hook_bool_tree_true
|
#undef TARGET_RETURN_IN_MEMORY
|
#undef TARGET_RETURN_IN_MEMORY
|
#define TARGET_RETURN_IN_MEMORY mn10300_return_in_memory
|
#define TARGET_RETURN_IN_MEMORY mn10300_return_in_memory
|
#undef TARGET_PASS_BY_REFERENCE
|
#undef TARGET_PASS_BY_REFERENCE
|
#define TARGET_PASS_BY_REFERENCE mn10300_pass_by_reference
|
#define TARGET_PASS_BY_REFERENCE mn10300_pass_by_reference
|
#undef TARGET_CALLEE_COPIES
|
#undef TARGET_CALLEE_COPIES
|
#define TARGET_CALLEE_COPIES hook_bool_CUMULATIVE_ARGS_mode_tree_bool_true
|
#define TARGET_CALLEE_COPIES hook_bool_CUMULATIVE_ARGS_mode_tree_bool_true
|
#undef TARGET_ARG_PARTIAL_BYTES
|
#undef TARGET_ARG_PARTIAL_BYTES
|
#define TARGET_ARG_PARTIAL_BYTES mn10300_arg_partial_bytes
|
#define TARGET_ARG_PARTIAL_BYTES mn10300_arg_partial_bytes
|
|
|
#undef TARGET_EXPAND_BUILTIN_SAVEREGS
|
#undef TARGET_EXPAND_BUILTIN_SAVEREGS
|
#define TARGET_EXPAND_BUILTIN_SAVEREGS mn10300_builtin_saveregs
|
#define TARGET_EXPAND_BUILTIN_SAVEREGS mn10300_builtin_saveregs
|
|
|
static void mn10300_encode_section_info (tree, rtx, int);
|
static void mn10300_encode_section_info (tree, rtx, int);
|
struct gcc_target targetm = TARGET_INITIALIZER;
|
struct gcc_target targetm = TARGET_INITIALIZER;
|
|
|
/* Implement TARGET_HANDLE_OPTION. */
|
/* Implement TARGET_HANDLE_OPTION. */
|
|
|
static bool
|
static bool
|
mn10300_handle_option (size_t code,
|
mn10300_handle_option (size_t code,
|
const char *arg ATTRIBUTE_UNUSED,
|
const char *arg ATTRIBUTE_UNUSED,
|
int value)
|
int value)
|
{
|
{
|
switch (code)
|
switch (code)
|
{
|
{
|
case OPT_mam33:
|
case OPT_mam33:
|
mn10300_processor = value ? PROCESSOR_AM33 : PROCESSOR_MN10300;
|
mn10300_processor = value ? PROCESSOR_AM33 : PROCESSOR_MN10300;
|
return true;
|
return true;
|
case OPT_mam33_2:
|
case OPT_mam33_2:
|
mn10300_processor = (value
|
mn10300_processor = (value
|
? PROCESSOR_AM33_2
|
? PROCESSOR_AM33_2
|
: MIN (PROCESSOR_AM33, PROCESSOR_DEFAULT));
|
: MIN (PROCESSOR_AM33, PROCESSOR_DEFAULT));
|
return true;
|
return true;
|
default:
|
default:
|
return true;
|
return true;
|
}
|
}
|
}
|
}
|
|
|
/* Implement OVERRIDE_OPTIONS. */
|
/* Implement OVERRIDE_OPTIONS. */
|
|
|
void
|
void
|
mn10300_override_options (void)
|
mn10300_override_options (void)
|
{
|
{
|
if (TARGET_AM33)
|
if (TARGET_AM33)
|
target_flags &= ~MASK_MULT_BUG;
|
target_flags &= ~MASK_MULT_BUG;
|
}
|
}
|
|
|
static void
|
static void
|
mn10300_file_start (void)
|
mn10300_file_start (void)
|
{
|
{
|
default_file_start ();
|
default_file_start ();
|
|
|
if (TARGET_AM33_2)
|
if (TARGET_AM33_2)
|
fprintf (asm_out_file, "\t.am33_2\n");
|
fprintf (asm_out_file, "\t.am33_2\n");
|
else if (TARGET_AM33)
|
else if (TARGET_AM33)
|
fprintf (asm_out_file, "\t.am33\n");
|
fprintf (asm_out_file, "\t.am33\n");
|
}
|
}
|
|
|
|
|
/* Print operand X using operand code CODE to assembly language output file
|
/* Print operand X using operand code CODE to assembly language output file
|
FILE. */
|
FILE. */
|
|
|
void
|
void
|
print_operand (FILE *file, rtx x, int code)
|
print_operand (FILE *file, rtx x, int code)
|
{
|
{
|
switch (code)
|
switch (code)
|
{
|
{
|
case 'b':
|
case 'b':
|
case 'B':
|
case 'B':
|
if (cc_status.mdep.fpCC)
|
if (cc_status.mdep.fpCC)
|
{
|
{
|
switch (code == 'b' ? GET_CODE (x)
|
switch (code == 'b' ? GET_CODE (x)
|
: reverse_condition_maybe_unordered (GET_CODE (x)))
|
: reverse_condition_maybe_unordered (GET_CODE (x)))
|
{
|
{
|
case NE:
|
case NE:
|
fprintf (file, "ne");
|
fprintf (file, "ne");
|
break;
|
break;
|
case EQ:
|
case EQ:
|
fprintf (file, "eq");
|
fprintf (file, "eq");
|
break;
|
break;
|
case GE:
|
case GE:
|
fprintf (file, "ge");
|
fprintf (file, "ge");
|
break;
|
break;
|
case GT:
|
case GT:
|
fprintf (file, "gt");
|
fprintf (file, "gt");
|
break;
|
break;
|
case LE:
|
case LE:
|
fprintf (file, "le");
|
fprintf (file, "le");
|
break;
|
break;
|
case LT:
|
case LT:
|
fprintf (file, "lt");
|
fprintf (file, "lt");
|
break;
|
break;
|
case ORDERED:
|
case ORDERED:
|
fprintf (file, "lge");
|
fprintf (file, "lge");
|
break;
|
break;
|
case UNORDERED:
|
case UNORDERED:
|
fprintf (file, "uo");
|
fprintf (file, "uo");
|
break;
|
break;
|
case LTGT:
|
case LTGT:
|
fprintf (file, "lg");
|
fprintf (file, "lg");
|
break;
|
break;
|
case UNEQ:
|
case UNEQ:
|
fprintf (file, "ue");
|
fprintf (file, "ue");
|
break;
|
break;
|
case UNGE:
|
case UNGE:
|
fprintf (file, "uge");
|
fprintf (file, "uge");
|
break;
|
break;
|
case UNGT:
|
case UNGT:
|
fprintf (file, "ug");
|
fprintf (file, "ug");
|
break;
|
break;
|
case UNLE:
|
case UNLE:
|
fprintf (file, "ule");
|
fprintf (file, "ule");
|
break;
|
break;
|
case UNLT:
|
case UNLT:
|
fprintf (file, "ul");
|
fprintf (file, "ul");
|
break;
|
break;
|
default:
|
default:
|
gcc_unreachable ();
|
gcc_unreachable ();
|
}
|
}
|
break;
|
break;
|
}
|
}
|
/* These are normal and reversed branches. */
|
/* These are normal and reversed branches. */
|
switch (code == 'b' ? GET_CODE (x) : reverse_condition (GET_CODE (x)))
|
switch (code == 'b' ? GET_CODE (x) : reverse_condition (GET_CODE (x)))
|
{
|
{
|
case NE:
|
case NE:
|
fprintf (file, "ne");
|
fprintf (file, "ne");
|
break;
|
break;
|
case EQ:
|
case EQ:
|
fprintf (file, "eq");
|
fprintf (file, "eq");
|
break;
|
break;
|
case GE:
|
case GE:
|
fprintf (file, "ge");
|
fprintf (file, "ge");
|
break;
|
break;
|
case GT:
|
case GT:
|
fprintf (file, "gt");
|
fprintf (file, "gt");
|
break;
|
break;
|
case LE:
|
case LE:
|
fprintf (file, "le");
|
fprintf (file, "le");
|
break;
|
break;
|
case LT:
|
case LT:
|
fprintf (file, "lt");
|
fprintf (file, "lt");
|
break;
|
break;
|
case GEU:
|
case GEU:
|
fprintf (file, "cc");
|
fprintf (file, "cc");
|
break;
|
break;
|
case GTU:
|
case GTU:
|
fprintf (file, "hi");
|
fprintf (file, "hi");
|
break;
|
break;
|
case LEU:
|
case LEU:
|
fprintf (file, "ls");
|
fprintf (file, "ls");
|
break;
|
break;
|
case LTU:
|
case LTU:
|
fprintf (file, "cs");
|
fprintf (file, "cs");
|
break;
|
break;
|
default:
|
default:
|
gcc_unreachable ();
|
gcc_unreachable ();
|
}
|
}
|
break;
|
break;
|
case 'C':
|
case 'C':
|
/* This is used for the operand to a call instruction;
|
/* This is used for the operand to a call instruction;
|
if it's a REG, enclose it in parens, else output
|
if it's a REG, enclose it in parens, else output
|
the operand normally. */
|
the operand normally. */
|
if (GET_CODE (x) == REG)
|
if (GET_CODE (x) == REG)
|
{
|
{
|
fputc ('(', file);
|
fputc ('(', file);
|
print_operand (file, x, 0);
|
print_operand (file, x, 0);
|
fputc (')', file);
|
fputc (')', file);
|
}
|
}
|
else
|
else
|
print_operand (file, x, 0);
|
print_operand (file, x, 0);
|
break;
|
break;
|
|
|
case 'D':
|
case 'D':
|
switch (GET_CODE (x))
|
switch (GET_CODE (x))
|
{
|
{
|
case MEM:
|
case MEM:
|
fputc ('(', file);
|
fputc ('(', file);
|
output_address (XEXP (x, 0));
|
output_address (XEXP (x, 0));
|
fputc (')', file);
|
fputc (')', file);
|
break;
|
break;
|
|
|
case REG:
|
case REG:
|
fprintf (file, "fd%d", REGNO (x) - 18);
|
fprintf (file, "fd%d", REGNO (x) - 18);
|
break;
|
break;
|
|
|
default:
|
default:
|
gcc_unreachable ();
|
gcc_unreachable ();
|
}
|
}
|
break;
|
break;
|
|
|
/* These are the least significant word in a 64bit value. */
|
/* These are the least significant word in a 64bit value. */
|
case 'L':
|
case 'L':
|
switch (GET_CODE (x))
|
switch (GET_CODE (x))
|
{
|
{
|
case MEM:
|
case MEM:
|
fputc ('(', file);
|
fputc ('(', file);
|
output_address (XEXP (x, 0));
|
output_address (XEXP (x, 0));
|
fputc (')', file);
|
fputc (')', file);
|
break;
|
break;
|
|
|
case REG:
|
case REG:
|
fprintf (file, "%s", reg_names[REGNO (x)]);
|
fprintf (file, "%s", reg_names[REGNO (x)]);
|
break;
|
break;
|
|
|
case SUBREG:
|
case SUBREG:
|
fprintf (file, "%s", reg_names[subreg_regno (x)]);
|
fprintf (file, "%s", reg_names[subreg_regno (x)]);
|
break;
|
break;
|
|
|
case CONST_DOUBLE:
|
case CONST_DOUBLE:
|
{
|
{
|
long val[2];
|
long val[2];
|
REAL_VALUE_TYPE rv;
|
REAL_VALUE_TYPE rv;
|
|
|
switch (GET_MODE (x))
|
switch (GET_MODE (x))
|
{
|
{
|
case DFmode:
|
case DFmode:
|
REAL_VALUE_FROM_CONST_DOUBLE (rv, x);
|
REAL_VALUE_FROM_CONST_DOUBLE (rv, x);
|
REAL_VALUE_TO_TARGET_DOUBLE (rv, val);
|
REAL_VALUE_TO_TARGET_DOUBLE (rv, val);
|
fprintf (file, "0x%lx", val[0]);
|
fprintf (file, "0x%lx", val[0]);
|
break;;
|
break;;
|
case SFmode:
|
case SFmode:
|
REAL_VALUE_FROM_CONST_DOUBLE (rv, x);
|
REAL_VALUE_FROM_CONST_DOUBLE (rv, x);
|
REAL_VALUE_TO_TARGET_SINGLE (rv, val[0]);
|
REAL_VALUE_TO_TARGET_SINGLE (rv, val[0]);
|
fprintf (file, "0x%lx", val[0]);
|
fprintf (file, "0x%lx", val[0]);
|
break;;
|
break;;
|
case VOIDmode:
|
case VOIDmode:
|
case DImode:
|
case DImode:
|
print_operand_address (file,
|
print_operand_address (file,
|
GEN_INT (CONST_DOUBLE_LOW (x)));
|
GEN_INT (CONST_DOUBLE_LOW (x)));
|
break;
|
break;
|
default:
|
default:
|
break;
|
break;
|
}
|
}
|
break;
|
break;
|
}
|
}
|
|
|
case CONST_INT:
|
case CONST_INT:
|
{
|
{
|
rtx low, high;
|
rtx low, high;
|
split_double (x, &low, &high);
|
split_double (x, &low, &high);
|
fprintf (file, "%ld", (long)INTVAL (low));
|
fprintf (file, "%ld", (long)INTVAL (low));
|
break;
|
break;
|
}
|
}
|
|
|
default:
|
default:
|
gcc_unreachable ();
|
gcc_unreachable ();
|
}
|
}
|
break;
|
break;
|
|
|
/* Similarly, but for the most significant word. */
|
/* Similarly, but for the most significant word. */
|
case 'H':
|
case 'H':
|
switch (GET_CODE (x))
|
switch (GET_CODE (x))
|
{
|
{
|
case MEM:
|
case MEM:
|
fputc ('(', file);
|
fputc ('(', file);
|
x = adjust_address (x, SImode, 4);
|
x = adjust_address (x, SImode, 4);
|
output_address (XEXP (x, 0));
|
output_address (XEXP (x, 0));
|
fputc (')', file);
|
fputc (')', file);
|
break;
|
break;
|
|
|
case REG:
|
case REG:
|
fprintf (file, "%s", reg_names[REGNO (x) + 1]);
|
fprintf (file, "%s", reg_names[REGNO (x) + 1]);
|
break;
|
break;
|
|
|
case SUBREG:
|
case SUBREG:
|
fprintf (file, "%s", reg_names[subreg_regno (x) + 1]);
|
fprintf (file, "%s", reg_names[subreg_regno (x) + 1]);
|
break;
|
break;
|
|
|
case CONST_DOUBLE:
|
case CONST_DOUBLE:
|
{
|
{
|
long val[2];
|
long val[2];
|
REAL_VALUE_TYPE rv;
|
REAL_VALUE_TYPE rv;
|
|
|
switch (GET_MODE (x))
|
switch (GET_MODE (x))
|
{
|
{
|
case DFmode:
|
case DFmode:
|
REAL_VALUE_FROM_CONST_DOUBLE (rv, x);
|
REAL_VALUE_FROM_CONST_DOUBLE (rv, x);
|
REAL_VALUE_TO_TARGET_DOUBLE (rv, val);
|
REAL_VALUE_TO_TARGET_DOUBLE (rv, val);
|
fprintf (file, "0x%lx", val[1]);
|
fprintf (file, "0x%lx", val[1]);
|
break;;
|
break;;
|
case SFmode:
|
case SFmode:
|
gcc_unreachable ();
|
gcc_unreachable ();
|
case VOIDmode:
|
case VOIDmode:
|
case DImode:
|
case DImode:
|
print_operand_address (file,
|
print_operand_address (file,
|
GEN_INT (CONST_DOUBLE_HIGH (x)));
|
GEN_INT (CONST_DOUBLE_HIGH (x)));
|
break;
|
break;
|
default:
|
default:
|
break;
|
break;
|
}
|
}
|
break;
|
break;
|
}
|
}
|
|
|
case CONST_INT:
|
case CONST_INT:
|
{
|
{
|
rtx low, high;
|
rtx low, high;
|
split_double (x, &low, &high);
|
split_double (x, &low, &high);
|
fprintf (file, "%ld", (long)INTVAL (high));
|
fprintf (file, "%ld", (long)INTVAL (high));
|
break;
|
break;
|
}
|
}
|
|
|
default:
|
default:
|
gcc_unreachable ();
|
gcc_unreachable ();
|
}
|
}
|
break;
|
break;
|
|
|
case 'A':
|
case 'A':
|
fputc ('(', file);
|
fputc ('(', file);
|
if (GET_CODE (XEXP (x, 0)) == REG)
|
if (GET_CODE (XEXP (x, 0)) == REG)
|
output_address (gen_rtx_PLUS (SImode, XEXP (x, 0), const0_rtx));
|
output_address (gen_rtx_PLUS (SImode, XEXP (x, 0), const0_rtx));
|
else
|
else
|
output_address (XEXP (x, 0));
|
output_address (XEXP (x, 0));
|
fputc (')', file);
|
fputc (')', file);
|
break;
|
break;
|
|
|
case 'N':
|
case 'N':
|
gcc_assert (INTVAL (x) >= -128 && INTVAL (x) <= 255);
|
gcc_assert (INTVAL (x) >= -128 && INTVAL (x) <= 255);
|
fprintf (file, "%d", (int)((~INTVAL (x)) & 0xff));
|
fprintf (file, "%d", (int)((~INTVAL (x)) & 0xff));
|
break;
|
break;
|
|
|
case 'U':
|
case 'U':
|
gcc_assert (INTVAL (x) >= -128 && INTVAL (x) <= 255);
|
gcc_assert (INTVAL (x) >= -128 && INTVAL (x) <= 255);
|
fprintf (file, "%d", (int)(INTVAL (x) & 0xff));
|
fprintf (file, "%d", (int)(INTVAL (x) & 0xff));
|
break;
|
break;
|
|
|
/* For shift counts. The hardware ignores the upper bits of
|
/* For shift counts. The hardware ignores the upper bits of
|
any immediate, but the assembler will flag an out of range
|
any immediate, but the assembler will flag an out of range
|
shift count as an error. So we mask off the high bits
|
shift count as an error. So we mask off the high bits
|
of the immediate here. */
|
of the immediate here. */
|
case 'S':
|
case 'S':
|
if (GET_CODE (x) == CONST_INT)
|
if (GET_CODE (x) == CONST_INT)
|
{
|
{
|
fprintf (file, "%d", (int)(INTVAL (x) & 0x1f));
|
fprintf (file, "%d", (int)(INTVAL (x) & 0x1f));
|
break;
|
break;
|
}
|
}
|
/* FALL THROUGH */
|
/* FALL THROUGH */
|
|
|
default:
|
default:
|
switch (GET_CODE (x))
|
switch (GET_CODE (x))
|
{
|
{
|
case MEM:
|
case MEM:
|
fputc ('(', file);
|
fputc ('(', file);
|
output_address (XEXP (x, 0));
|
output_address (XEXP (x, 0));
|
fputc (')', file);
|
fputc (')', file);
|
break;
|
break;
|
|
|
case PLUS:
|
case PLUS:
|
output_address (x);
|
output_address (x);
|
break;
|
break;
|
|
|
case REG:
|
case REG:
|
fprintf (file, "%s", reg_names[REGNO (x)]);
|
fprintf (file, "%s", reg_names[REGNO (x)]);
|
break;
|
break;
|
|
|
case SUBREG:
|
case SUBREG:
|
fprintf (file, "%s", reg_names[subreg_regno (x)]);
|
fprintf (file, "%s", reg_names[subreg_regno (x)]);
|
break;
|
break;
|
|
|
/* This will only be single precision.... */
|
/* This will only be single precision.... */
|
case CONST_DOUBLE:
|
case CONST_DOUBLE:
|
{
|
{
|
unsigned long val;
|
unsigned long val;
|
REAL_VALUE_TYPE rv;
|
REAL_VALUE_TYPE rv;
|
|
|
REAL_VALUE_FROM_CONST_DOUBLE (rv, x);
|
REAL_VALUE_FROM_CONST_DOUBLE (rv, x);
|
REAL_VALUE_TO_TARGET_SINGLE (rv, val);
|
REAL_VALUE_TO_TARGET_SINGLE (rv, val);
|
fprintf (file, "0x%lx", val);
|
fprintf (file, "0x%lx", val);
|
break;
|
break;
|
}
|
}
|
|
|
case CONST_INT:
|
case CONST_INT:
|
case SYMBOL_REF:
|
case SYMBOL_REF:
|
case CONST:
|
case CONST:
|
case LABEL_REF:
|
case LABEL_REF:
|
case CODE_LABEL:
|
case CODE_LABEL:
|
case UNSPEC:
|
case UNSPEC:
|
print_operand_address (file, x);
|
print_operand_address (file, x);
|
break;
|
break;
|
default:
|
default:
|
gcc_unreachable ();
|
gcc_unreachable ();
|
}
|
}
|
break;
|
break;
|
}
|
}
|
}
|
}
|
|
|
/* Output assembly language output for the address ADDR to FILE. */
|
/* Output assembly language output for the address ADDR to FILE. */
|
|
|
void
|
void
|
print_operand_address (FILE *file, rtx addr)
|
print_operand_address (FILE *file, rtx addr)
|
{
|
{
|
switch (GET_CODE (addr))
|
switch (GET_CODE (addr))
|
{
|
{
|
case POST_INC:
|
case POST_INC:
|
print_operand_address (file, XEXP (addr, 0));
|
print_operand_address (file, XEXP (addr, 0));
|
fputc ('+', file);
|
fputc ('+', file);
|
break;
|
break;
|
case REG:
|
case REG:
|
print_operand (file, addr, 0);
|
print_operand (file, addr, 0);
|
break;
|
break;
|
case PLUS:
|
case PLUS:
|
{
|
{
|
rtx base, index;
|
rtx base, index;
|
if (REG_P (XEXP (addr, 0))
|
if (REG_P (XEXP (addr, 0))
|
&& REG_OK_FOR_BASE_P (XEXP (addr, 0)))
|
&& REG_OK_FOR_BASE_P (XEXP (addr, 0)))
|
base = XEXP (addr, 0), index = XEXP (addr, 1);
|
base = XEXP (addr, 0), index = XEXP (addr, 1);
|
else if (REG_P (XEXP (addr, 1))
|
else if (REG_P (XEXP (addr, 1))
|
&& REG_OK_FOR_BASE_P (XEXP (addr, 1)))
|
&& REG_OK_FOR_BASE_P (XEXP (addr, 1)))
|
base = XEXP (addr, 1), index = XEXP (addr, 0);
|
base = XEXP (addr, 1), index = XEXP (addr, 0);
|
else
|
else
|
gcc_unreachable ();
|
gcc_unreachable ();
|
print_operand (file, index, 0);
|
print_operand (file, index, 0);
|
fputc (',', file);
|
fputc (',', file);
|
print_operand (file, base, 0);;
|
print_operand (file, base, 0);;
|
break;
|
break;
|
}
|
}
|
case SYMBOL_REF:
|
case SYMBOL_REF:
|
output_addr_const (file, addr);
|
output_addr_const (file, addr);
|
break;
|
break;
|
default:
|
default:
|
output_addr_const (file, addr);
|
output_addr_const (file, addr);
|
break;
|
break;
|
}
|
}
|
}
|
}
|
|
|
/* Count the number of FP registers that have to be saved. */
|
/* Count the number of FP registers that have to be saved. */
|
static int
|
static int
|
fp_regs_to_save (void)
|
fp_regs_to_save (void)
|
{
|
{
|
int i, n = 0;
|
int i, n = 0;
|
|
|
if (! TARGET_AM33_2)
|
if (! TARGET_AM33_2)
|
return 0;
|
return 0;
|
|
|
for (i = FIRST_FP_REGNUM; i <= LAST_FP_REGNUM; ++i)
|
for (i = FIRST_FP_REGNUM; i <= LAST_FP_REGNUM; ++i)
|
if (regs_ever_live[i] && ! call_used_regs[i])
|
if (regs_ever_live[i] && ! call_used_regs[i])
|
++n;
|
++n;
|
|
|
return n;
|
return n;
|
}
|
}
|
|
|
/* Print a set of registers in the format required by "movm" and "ret".
|
/* Print a set of registers in the format required by "movm" and "ret".
|
Register K is saved if bit K of MASK is set. The data and address
|
Register K is saved if bit K of MASK is set. The data and address
|
registers can be stored individually, but the extended registers cannot.
|
registers can be stored individually, but the extended registers cannot.
|
We assume that the mask alread takes that into account. For instance,
|
We assume that the mask alread takes that into account. For instance,
|
bits 14 to 17 must have the same value. */
|
bits 14 to 17 must have the same value. */
|
|
|
void
|
void
|
mn10300_print_reg_list (FILE *file, int mask)
|
mn10300_print_reg_list (FILE *file, int mask)
|
{
|
{
|
int need_comma;
|
int need_comma;
|
int i;
|
int i;
|
|
|
need_comma = 0;
|
need_comma = 0;
|
fputc ('[', file);
|
fputc ('[', file);
|
|
|
for (i = 0; i < FIRST_EXTENDED_REGNUM; i++)
|
for (i = 0; i < FIRST_EXTENDED_REGNUM; i++)
|
if ((mask & (1 << i)) != 0)
|
if ((mask & (1 << i)) != 0)
|
{
|
{
|
if (need_comma)
|
if (need_comma)
|
fputc (',', file);
|
fputc (',', file);
|
fputs (reg_names [i], file);
|
fputs (reg_names [i], file);
|
need_comma = 1;
|
need_comma = 1;
|
}
|
}
|
|
|
if ((mask & 0x3c000) != 0)
|
if ((mask & 0x3c000) != 0)
|
{
|
{
|
gcc_assert ((mask & 0x3c000) == 0x3c000);
|
gcc_assert ((mask & 0x3c000) == 0x3c000);
|
if (need_comma)
|
if (need_comma)
|
fputc (',', file);
|
fputc (',', file);
|
fputs ("exreg1", file);
|
fputs ("exreg1", file);
|
need_comma = 1;
|
need_comma = 1;
|
}
|
}
|
|
|
fputc (']', file);
|
fputc (']', file);
|
}
|
}
|
|
|
int
|
int
|
can_use_return_insn (void)
|
can_use_return_insn (void)
|
{
|
{
|
/* size includes the fixed stack space needed for function calls. */
|
/* size includes the fixed stack space needed for function calls. */
|
int size = get_frame_size () + current_function_outgoing_args_size;
|
int size = get_frame_size () + current_function_outgoing_args_size;
|
|
|
/* And space for the return pointer. */
|
/* And space for the return pointer. */
|
size += current_function_outgoing_args_size ? 4 : 0;
|
size += current_function_outgoing_args_size ? 4 : 0;
|
|
|
return (reload_completed
|
return (reload_completed
|
&& size == 0
|
&& size == 0
|
&& !regs_ever_live[2]
|
&& !regs_ever_live[2]
|
&& !regs_ever_live[3]
|
&& !regs_ever_live[3]
|
&& !regs_ever_live[6]
|
&& !regs_ever_live[6]
|
&& !regs_ever_live[7]
|
&& !regs_ever_live[7]
|
&& !regs_ever_live[14]
|
&& !regs_ever_live[14]
|
&& !regs_ever_live[15]
|
&& !regs_ever_live[15]
|
&& !regs_ever_live[16]
|
&& !regs_ever_live[16]
|
&& !regs_ever_live[17]
|
&& !regs_ever_live[17]
|
&& fp_regs_to_save () == 0
|
&& fp_regs_to_save () == 0
|
&& !frame_pointer_needed);
|
&& !frame_pointer_needed);
|
}
|
}
|
|
|
/* Returns the set of live, callee-saved registers as a bitmask. The
|
/* Returns the set of live, callee-saved registers as a bitmask. The
|
callee-saved extended registers cannot be stored individually, so
|
callee-saved extended registers cannot be stored individually, so
|
all of them will be included in the mask if any one of them is used. */
|
all of them will be included in the mask if any one of them is used. */
|
|
|
int
|
int
|
mn10300_get_live_callee_saved_regs (void)
|
mn10300_get_live_callee_saved_regs (void)
|
{
|
{
|
int mask;
|
int mask;
|
int i;
|
int i;
|
|
|
mask = 0;
|
mask = 0;
|
for (i = 0; i <= LAST_EXTENDED_REGNUM; i++)
|
for (i = 0; i <= LAST_EXTENDED_REGNUM; i++)
|
if (regs_ever_live[i] && ! call_used_regs[i])
|
if (regs_ever_live[i] && ! call_used_regs[i])
|
mask |= (1 << i);
|
mask |= (1 << i);
|
if ((mask & 0x3c000) != 0)
|
if ((mask & 0x3c000) != 0)
|
mask |= 0x3c000;
|
mask |= 0x3c000;
|
|
|
return mask;
|
return mask;
|
}
|
}
|
|
|
/* Generate an instruction that pushes several registers onto the stack.
|
/* Generate an instruction that pushes several registers onto the stack.
|
Register K will be saved if bit K in MASK is set. The function does
|
Register K will be saved if bit K in MASK is set. The function does
|
nothing if MASK is zero.
|
nothing if MASK is zero.
|
|
|
To be compatible with the "movm" instruction, the lowest-numbered
|
To be compatible with the "movm" instruction, the lowest-numbered
|
register must be stored in the lowest slot. If MASK is the set
|
register must be stored in the lowest slot. If MASK is the set
|
{ R1,...,RN }, where R1...RN are ordered least first, the generated
|
{ R1,...,RN }, where R1...RN are ordered least first, the generated
|
instruction will have the form:
|
instruction will have the form:
|
|
|
(parallel
|
(parallel
|
(set (reg:SI 9) (plus:SI (reg:SI 9) (const_int -N*4)))
|
(set (reg:SI 9) (plus:SI (reg:SI 9) (const_int -N*4)))
|
(set (mem:SI (plus:SI (reg:SI 9)
|
(set (mem:SI (plus:SI (reg:SI 9)
|
(const_int -1*4)))
|
(const_int -1*4)))
|
(reg:SI RN))
|
(reg:SI RN))
|
...
|
...
|
(set (mem:SI (plus:SI (reg:SI 9)
|
(set (mem:SI (plus:SI (reg:SI 9)
|
(const_int -N*4)))
|
(const_int -N*4)))
|
(reg:SI R1))) */
|
(reg:SI R1))) */
|
|
|
void
|
void
|
mn10300_gen_multiple_store (int mask)
|
mn10300_gen_multiple_store (int mask)
|
{
|
{
|
if (mask != 0)
|
if (mask != 0)
|
{
|
{
|
int i;
|
int i;
|
int count;
|
int count;
|
rtx par;
|
rtx par;
|
int pari;
|
int pari;
|
|
|
/* Count how many registers need to be saved. */
|
/* Count how many registers need to be saved. */
|
count = 0;
|
count = 0;
|
for (i = 0; i <= LAST_EXTENDED_REGNUM; i++)
|
for (i = 0; i <= LAST_EXTENDED_REGNUM; i++)
|
if ((mask & (1 << i)) != 0)
|
if ((mask & (1 << i)) != 0)
|
count += 1;
|
count += 1;
|
|
|
/* We need one PARALLEL element to update the stack pointer and
|
/* We need one PARALLEL element to update the stack pointer and
|
an additional element for each register that is stored. */
|
an additional element for each register that is stored. */
|
par = gen_rtx_PARALLEL (VOIDmode, rtvec_alloc (count + 1));
|
par = gen_rtx_PARALLEL (VOIDmode, rtvec_alloc (count + 1));
|
|
|
/* Create the instruction that updates the stack pointer. */
|
/* Create the instruction that updates the stack pointer. */
|
XVECEXP (par, 0, 0)
|
XVECEXP (par, 0, 0)
|
= gen_rtx_SET (SImode,
|
= gen_rtx_SET (SImode,
|
stack_pointer_rtx,
|
stack_pointer_rtx,
|
gen_rtx_PLUS (SImode,
|
gen_rtx_PLUS (SImode,
|
stack_pointer_rtx,
|
stack_pointer_rtx,
|
GEN_INT (-count * 4)));
|
GEN_INT (-count * 4)));
|
|
|
/* Create each store. */
|
/* Create each store. */
|
pari = 1;
|
pari = 1;
|
for (i = LAST_EXTENDED_REGNUM; i >= 0; i--)
|
for (i = LAST_EXTENDED_REGNUM; i >= 0; i--)
|
if ((mask & (1 << i)) != 0)
|
if ((mask & (1 << i)) != 0)
|
{
|
{
|
rtx address = gen_rtx_PLUS (SImode,
|
rtx address = gen_rtx_PLUS (SImode,
|
stack_pointer_rtx,
|
stack_pointer_rtx,
|
GEN_INT (-pari * 4));
|
GEN_INT (-pari * 4));
|
XVECEXP(par, 0, pari)
|
XVECEXP(par, 0, pari)
|
= gen_rtx_SET (VOIDmode,
|
= gen_rtx_SET (VOIDmode,
|
gen_rtx_MEM (SImode, address),
|
gen_rtx_MEM (SImode, address),
|
gen_rtx_REG (SImode, i));
|
gen_rtx_REG (SImode, i));
|
pari += 1;
|
pari += 1;
|
}
|
}
|
|
|
par = emit_insn (par);
|
par = emit_insn (par);
|
RTX_FRAME_RELATED_P (par) = 1;
|
RTX_FRAME_RELATED_P (par) = 1;
|
}
|
}
|
}
|
}
|
|
|
void
|
void
|
expand_prologue (void)
|
expand_prologue (void)
|
{
|
{
|
HOST_WIDE_INT size;
|
HOST_WIDE_INT size;
|
|
|
/* SIZE includes the fixed stack space needed for function calls. */
|
/* SIZE includes the fixed stack space needed for function calls. */
|
size = get_frame_size () + current_function_outgoing_args_size;
|
size = get_frame_size () + current_function_outgoing_args_size;
|
size += (current_function_outgoing_args_size ? 4 : 0);
|
size += (current_function_outgoing_args_size ? 4 : 0);
|
|
|
/* If we use any of the callee-saved registers, save them now. */
|
/* If we use any of the callee-saved registers, save them now. */
|
mn10300_gen_multiple_store (mn10300_get_live_callee_saved_regs ());
|
mn10300_gen_multiple_store (mn10300_get_live_callee_saved_regs ());
|
|
|
if (TARGET_AM33_2 && fp_regs_to_save ())
|
if (TARGET_AM33_2 && fp_regs_to_save ())
|
{
|
{
|
int num_regs_to_save = fp_regs_to_save (), i;
|
int num_regs_to_save = fp_regs_to_save (), i;
|
HOST_WIDE_INT xsize;
|
HOST_WIDE_INT xsize;
|
enum { save_sp_merge,
|
enum { save_sp_merge,
|
save_sp_no_merge,
|
save_sp_no_merge,
|
save_sp_partial_merge,
|
save_sp_partial_merge,
|
save_a0_merge,
|
save_a0_merge,
|
save_a0_no_merge } strategy;
|
save_a0_no_merge } strategy;
|
unsigned int strategy_size = (unsigned)-1, this_strategy_size;
|
unsigned int strategy_size = (unsigned)-1, this_strategy_size;
|
rtx reg;
|
rtx reg;
|
rtx insn;
|
rtx insn;
|
|
|
/* We have several different strategies to save FP registers.
|
/* We have several different strategies to save FP registers.
|
We can store them using SP offsets, which is beneficial if
|
We can store them using SP offsets, which is beneficial if
|
there are just a few registers to save, or we can use `a0' in
|
there are just a few registers to save, or we can use `a0' in
|
post-increment mode (`a0' is the only call-clobbered address
|
post-increment mode (`a0' is the only call-clobbered address
|
register that is never used to pass information to a
|
register that is never used to pass information to a
|
function). Furthermore, if we don't need a frame pointer, we
|
function). Furthermore, if we don't need a frame pointer, we
|
can merge the two SP adds into a single one, but this isn't
|
can merge the two SP adds into a single one, but this isn't
|
always beneficial; sometimes we can just split the two adds
|
always beneficial; sometimes we can just split the two adds
|
so that we don't exceed a 16-bit constant size. The code
|
so that we don't exceed a 16-bit constant size. The code
|
below will select which strategy to use, so as to generate
|
below will select which strategy to use, so as to generate
|
smallest code. Ties are broken in favor or shorter sequences
|
smallest code. Ties are broken in favor or shorter sequences
|
(in terms of number of instructions). */
|
(in terms of number of instructions). */
|
|
|
#define SIZE_ADD_AX(S) ((((S) >= (1 << 15)) || ((S) < -(1 << 15))) ? 6 \
|
#define SIZE_ADD_AX(S) ((((S) >= (1 << 15)) || ((S) < -(1 << 15))) ? 6 \
|
: (((S) >= (1 << 7)) || ((S) < -(1 << 7))) ? 4 : 2)
|
: (((S) >= (1 << 7)) || ((S) < -(1 << 7))) ? 4 : 2)
|
#define SIZE_ADD_SP(S) ((((S) >= (1 << 15)) || ((S) < -(1 << 15))) ? 6 \
|
#define SIZE_ADD_SP(S) ((((S) >= (1 << 15)) || ((S) < -(1 << 15))) ? 6 \
|
: (((S) >= (1 << 7)) || ((S) < -(1 << 7))) ? 4 : 3)
|
: (((S) >= (1 << 7)) || ((S) < -(1 << 7))) ? 4 : 3)
|
#define SIZE_FMOV_LIMIT(S,N,L,SIZE1,SIZE2,ELSE) \
|
#define SIZE_FMOV_LIMIT(S,N,L,SIZE1,SIZE2,ELSE) \
|
(((S) >= (L)) ? (SIZE1) * (N) \
|
(((S) >= (L)) ? (SIZE1) * (N) \
|
: ((S) + 4 * (N) >= (L)) ? (((L) - (S)) / 4 * (SIZE2) \
|
: ((S) + 4 * (N) >= (L)) ? (((L) - (S)) / 4 * (SIZE2) \
|
+ ((S) + 4 * (N) - (L)) / 4 * (SIZE1)) \
|
+ ((S) + 4 * (N) - (L)) / 4 * (SIZE1)) \
|
: (ELSE))
|
: (ELSE))
|
#define SIZE_FMOV_SP_(S,N) \
|
#define SIZE_FMOV_SP_(S,N) \
|
(SIZE_FMOV_LIMIT ((S), (N), (1 << 24), 7, 6, \
|
(SIZE_FMOV_LIMIT ((S), (N), (1 << 24), 7, 6, \
|
SIZE_FMOV_LIMIT ((S), (N), (1 << 8), 6, 4, \
|
SIZE_FMOV_LIMIT ((S), (N), (1 << 8), 6, 4, \
|
(S) ? 4 * (N) : 3 + 4 * ((N) - 1))))
|
(S) ? 4 * (N) : 3 + 4 * ((N) - 1))))
|
#define SIZE_FMOV_SP(S,N) (SIZE_FMOV_SP_ ((unsigned HOST_WIDE_INT)(S), (N)))
|
#define SIZE_FMOV_SP(S,N) (SIZE_FMOV_SP_ ((unsigned HOST_WIDE_INT)(S), (N)))
|
|
|
/* Consider alternative save_sp_merge only if we don't need the
|
/* Consider alternative save_sp_merge only if we don't need the
|
frame pointer and size is nonzero. */
|
frame pointer and size is nonzero. */
|
if (! frame_pointer_needed && size)
|
if (! frame_pointer_needed && size)
|
{
|
{
|
/* Insn: add -(size + 4 * num_regs_to_save), sp. */
|
/* Insn: add -(size + 4 * num_regs_to_save), sp. */
|
this_strategy_size = SIZE_ADD_SP (-(size + 4 * num_regs_to_save));
|
this_strategy_size = SIZE_ADD_SP (-(size + 4 * num_regs_to_save));
|
/* Insn: fmov fs#, (##, sp), for each fs# to be saved. */
|
/* Insn: fmov fs#, (##, sp), for each fs# to be saved. */
|
this_strategy_size += SIZE_FMOV_SP (size, num_regs_to_save);
|
this_strategy_size += SIZE_FMOV_SP (size, num_regs_to_save);
|
|
|
if (this_strategy_size < strategy_size)
|
if (this_strategy_size < strategy_size)
|
{
|
{
|
strategy = save_sp_merge;
|
strategy = save_sp_merge;
|
strategy_size = this_strategy_size;
|
strategy_size = this_strategy_size;
|
}
|
}
|
}
|
}
|
|
|
/* Consider alternative save_sp_no_merge unconditionally. */
|
/* Consider alternative save_sp_no_merge unconditionally. */
|
/* Insn: add -4 * num_regs_to_save, sp. */
|
/* Insn: add -4 * num_regs_to_save, sp. */
|
this_strategy_size = SIZE_ADD_SP (-4 * num_regs_to_save);
|
this_strategy_size = SIZE_ADD_SP (-4 * num_regs_to_save);
|
/* Insn: fmov fs#, (##, sp), for each fs# to be saved. */
|
/* Insn: fmov fs#, (##, sp), for each fs# to be saved. */
|
this_strategy_size += SIZE_FMOV_SP (0, num_regs_to_save);
|
this_strategy_size += SIZE_FMOV_SP (0, num_regs_to_save);
|
if (size)
|
if (size)
|
{
|
{
|
/* Insn: add -size, sp. */
|
/* Insn: add -size, sp. */
|
this_strategy_size += SIZE_ADD_SP (-size);
|
this_strategy_size += SIZE_ADD_SP (-size);
|
}
|
}
|
|
|
if (this_strategy_size < strategy_size)
|
if (this_strategy_size < strategy_size)
|
{
|
{
|
strategy = save_sp_no_merge;
|
strategy = save_sp_no_merge;
|
strategy_size = this_strategy_size;
|
strategy_size = this_strategy_size;
|
}
|
}
|
|
|
/* Consider alternative save_sp_partial_merge only if we don't
|
/* Consider alternative save_sp_partial_merge only if we don't
|
need a frame pointer and size is reasonably large. */
|
need a frame pointer and size is reasonably large. */
|
if (! frame_pointer_needed && size + 4 * num_regs_to_save > 128)
|
if (! frame_pointer_needed && size + 4 * num_regs_to_save > 128)
|
{
|
{
|
/* Insn: add -128, sp. */
|
/* Insn: add -128, sp. */
|
this_strategy_size = SIZE_ADD_SP (-128);
|
this_strategy_size = SIZE_ADD_SP (-128);
|
/* Insn: fmov fs#, (##, sp), for each fs# to be saved. */
|
/* Insn: fmov fs#, (##, sp), for each fs# to be saved. */
|
this_strategy_size += SIZE_FMOV_SP (128 - 4 * num_regs_to_save,
|
this_strategy_size += SIZE_FMOV_SP (128 - 4 * num_regs_to_save,
|
num_regs_to_save);
|
num_regs_to_save);
|
if (size)
|
if (size)
|
{
|
{
|
/* Insn: add 128-size, sp. */
|
/* Insn: add 128-size, sp. */
|
this_strategy_size += SIZE_ADD_SP (128 - size);
|
this_strategy_size += SIZE_ADD_SP (128 - size);
|
}
|
}
|
|
|
if (this_strategy_size < strategy_size)
|
if (this_strategy_size < strategy_size)
|
{
|
{
|
strategy = save_sp_partial_merge;
|
strategy = save_sp_partial_merge;
|
strategy_size = this_strategy_size;
|
strategy_size = this_strategy_size;
|
}
|
}
|
}
|
}
|
|
|
/* Consider alternative save_a0_merge only if we don't need a
|
/* Consider alternative save_a0_merge only if we don't need a
|
frame pointer, size is nonzero and the user hasn't
|
frame pointer, size is nonzero and the user hasn't
|
changed the calling conventions of a0. */
|
changed the calling conventions of a0. */
|
if (! frame_pointer_needed && size
|
if (! frame_pointer_needed && size
|
&& call_used_regs[FIRST_ADDRESS_REGNUM]
|
&& call_used_regs[FIRST_ADDRESS_REGNUM]
|
&& ! fixed_regs[FIRST_ADDRESS_REGNUM])
|
&& ! fixed_regs[FIRST_ADDRESS_REGNUM])
|
{
|
{
|
/* Insn: add -(size + 4 * num_regs_to_save), sp. */
|
/* Insn: add -(size + 4 * num_regs_to_save), sp. */
|
this_strategy_size = SIZE_ADD_SP (-(size + 4 * num_regs_to_save));
|
this_strategy_size = SIZE_ADD_SP (-(size + 4 * num_regs_to_save));
|
/* Insn: mov sp, a0. */
|
/* Insn: mov sp, a0. */
|
this_strategy_size++;
|
this_strategy_size++;
|
if (size)
|
if (size)
|
{
|
{
|
/* Insn: add size, a0. */
|
/* Insn: add size, a0. */
|
this_strategy_size += SIZE_ADD_AX (size);
|
this_strategy_size += SIZE_ADD_AX (size);
|
}
|
}
|
/* Insn: fmov fs#, (a0+), for each fs# to be saved. */
|
/* Insn: fmov fs#, (a0+), for each fs# to be saved. */
|
this_strategy_size += 3 * num_regs_to_save;
|
this_strategy_size += 3 * num_regs_to_save;
|
|
|
if (this_strategy_size < strategy_size)
|
if (this_strategy_size < strategy_size)
|
{
|
{
|
strategy = save_a0_merge;
|
strategy = save_a0_merge;
|
strategy_size = this_strategy_size;
|
strategy_size = this_strategy_size;
|
}
|
}
|
}
|
}
|
|
|
/* Consider alternative save_a0_no_merge if the user hasn't
|
/* Consider alternative save_a0_no_merge if the user hasn't
|
changed the calling conventions of a0. */
|
changed the calling conventions of a0. */
|
if (call_used_regs[FIRST_ADDRESS_REGNUM]
|
if (call_used_regs[FIRST_ADDRESS_REGNUM]
|
&& ! fixed_regs[FIRST_ADDRESS_REGNUM])
|
&& ! fixed_regs[FIRST_ADDRESS_REGNUM])
|
{
|
{
|
/* Insn: add -4 * num_regs_to_save, sp. */
|
/* Insn: add -4 * num_regs_to_save, sp. */
|
this_strategy_size = SIZE_ADD_SP (-4 * num_regs_to_save);
|
this_strategy_size = SIZE_ADD_SP (-4 * num_regs_to_save);
|
/* Insn: mov sp, a0. */
|
/* Insn: mov sp, a0. */
|
this_strategy_size++;
|
this_strategy_size++;
|
/* Insn: fmov fs#, (a0+), for each fs# to be saved. */
|
/* Insn: fmov fs#, (a0+), for each fs# to be saved. */
|
this_strategy_size += 3 * num_regs_to_save;
|
this_strategy_size += 3 * num_regs_to_save;
|
if (size)
|
if (size)
|
{
|
{
|
/* Insn: add -size, sp. */
|
/* Insn: add -size, sp. */
|
this_strategy_size += SIZE_ADD_SP (-size);
|
this_strategy_size += SIZE_ADD_SP (-size);
|
}
|
}
|
|
|
if (this_strategy_size < strategy_size)
|
if (this_strategy_size < strategy_size)
|
{
|
{
|
strategy = save_a0_no_merge;
|
strategy = save_a0_no_merge;
|
strategy_size = this_strategy_size;
|
strategy_size = this_strategy_size;
|
}
|
}
|
}
|
}
|
|
|
/* Emit the initial SP add, common to all strategies. */
|
/* Emit the initial SP add, common to all strategies. */
|
switch (strategy)
|
switch (strategy)
|
{
|
{
|
case save_sp_no_merge:
|
case save_sp_no_merge:
|
case save_a0_no_merge:
|
case save_a0_no_merge:
|
emit_insn (gen_addsi3 (stack_pointer_rtx,
|
emit_insn (gen_addsi3 (stack_pointer_rtx,
|
stack_pointer_rtx,
|
stack_pointer_rtx,
|
GEN_INT (-4 * num_regs_to_save)));
|
GEN_INT (-4 * num_regs_to_save)));
|
xsize = 0;
|
xsize = 0;
|
break;
|
break;
|
|
|
case save_sp_partial_merge:
|
case save_sp_partial_merge:
|
emit_insn (gen_addsi3 (stack_pointer_rtx,
|
emit_insn (gen_addsi3 (stack_pointer_rtx,
|
stack_pointer_rtx,
|
stack_pointer_rtx,
|
GEN_INT (-128)));
|
GEN_INT (-128)));
|
xsize = 128 - 4 * num_regs_to_save;
|
xsize = 128 - 4 * num_regs_to_save;
|
size -= xsize;
|
size -= xsize;
|
break;
|
break;
|
|
|
case save_sp_merge:
|
case save_sp_merge:
|
case save_a0_merge:
|
case save_a0_merge:
|
emit_insn (gen_addsi3 (stack_pointer_rtx,
|
emit_insn (gen_addsi3 (stack_pointer_rtx,
|
stack_pointer_rtx,
|
stack_pointer_rtx,
|
GEN_INT (-(size + 4 * num_regs_to_save))));
|
GEN_INT (-(size + 4 * num_regs_to_save))));
|
/* We'll have to adjust FP register saves according to the
|
/* We'll have to adjust FP register saves according to the
|
frame size. */
|
frame size. */
|
xsize = size;
|
xsize = size;
|
/* Since we've already created the stack frame, don't do it
|
/* Since we've already created the stack frame, don't do it
|
again at the end of the function. */
|
again at the end of the function. */
|
size = 0;
|
size = 0;
|
break;
|
break;
|
|
|
default:
|
default:
|
gcc_unreachable ();
|
gcc_unreachable ();
|
}
|
}
|
|
|
/* Now prepare register a0, if we have decided to use it. */
|
/* Now prepare register a0, if we have decided to use it. */
|
switch (strategy)
|
switch (strategy)
|
{
|
{
|
case save_sp_merge:
|
case save_sp_merge:
|
case save_sp_no_merge:
|
case save_sp_no_merge:
|
case save_sp_partial_merge:
|
case save_sp_partial_merge:
|
reg = 0;
|
reg = 0;
|
break;
|
break;
|
|
|
case save_a0_merge:
|
case save_a0_merge:
|
case save_a0_no_merge:
|
case save_a0_no_merge:
|
reg = gen_rtx_REG (SImode, FIRST_ADDRESS_REGNUM);
|
reg = gen_rtx_REG (SImode, FIRST_ADDRESS_REGNUM);
|
emit_insn (gen_movsi (reg, stack_pointer_rtx));
|
emit_insn (gen_movsi (reg, stack_pointer_rtx));
|
if (xsize)
|
if (xsize)
|
emit_insn (gen_addsi3 (reg, reg, GEN_INT (xsize)));
|
emit_insn (gen_addsi3 (reg, reg, GEN_INT (xsize)));
|
reg = gen_rtx_POST_INC (SImode, reg);
|
reg = gen_rtx_POST_INC (SImode, reg);
|
break;
|
break;
|
|
|
default:
|
default:
|
gcc_unreachable ();
|
gcc_unreachable ();
|
}
|
}
|
|
|
/* Now actually save the FP registers. */
|
/* Now actually save the FP registers. */
|
for (i = FIRST_FP_REGNUM; i <= LAST_FP_REGNUM; ++i)
|
for (i = FIRST_FP_REGNUM; i <= LAST_FP_REGNUM; ++i)
|
if (regs_ever_live[i] && ! call_used_regs[i])
|
if (regs_ever_live[i] && ! call_used_regs[i])
|
{
|
{
|
rtx addr;
|
rtx addr;
|
|
|
if (reg)
|
if (reg)
|
addr = reg;
|
addr = reg;
|
else
|
else
|
{
|
{
|
/* If we aren't using `a0', use an SP offset. */
|
/* If we aren't using `a0', use an SP offset. */
|
if (xsize)
|
if (xsize)
|
{
|
{
|
addr = gen_rtx_PLUS (SImode,
|
addr = gen_rtx_PLUS (SImode,
|
stack_pointer_rtx,
|
stack_pointer_rtx,
|
GEN_INT (xsize));
|
GEN_INT (xsize));
|
}
|
}
|
else
|
else
|
addr = stack_pointer_rtx;
|
addr = stack_pointer_rtx;
|
|
|
xsize += 4;
|
xsize += 4;
|
}
|
}
|
|
|
insn = emit_insn (gen_movsi (gen_rtx_MEM (SImode, addr),
|
insn = emit_insn (gen_movsi (gen_rtx_MEM (SImode, addr),
|
gen_rtx_REG (SImode, i)));
|
gen_rtx_REG (SImode, i)));
|
|
|
RTX_FRAME_RELATED_P (insn) = 1;
|
RTX_FRAME_RELATED_P (insn) = 1;
|
}
|
}
|
}
|
}
|
|
|
/* Now put the frame pointer into the frame pointer register. */
|
/* Now put the frame pointer into the frame pointer register. */
|
if (frame_pointer_needed)
|
if (frame_pointer_needed)
|
emit_move_insn (frame_pointer_rtx, stack_pointer_rtx);
|
emit_move_insn (frame_pointer_rtx, stack_pointer_rtx);
|
|
|
/* Allocate stack for this frame. */
|
/* Allocate stack for this frame. */
|
if (size)
|
if (size)
|
emit_insn (gen_addsi3 (stack_pointer_rtx,
|
emit_insn (gen_addsi3 (stack_pointer_rtx,
|
stack_pointer_rtx,
|
stack_pointer_rtx,
|
GEN_INT (-size)));
|
GEN_INT (-size)));
|
if (flag_pic && regs_ever_live[PIC_OFFSET_TABLE_REGNUM])
|
if (flag_pic && regs_ever_live[PIC_OFFSET_TABLE_REGNUM])
|
{
|
{
|
rtx insn = get_last_insn ();
|
rtx insn = get_last_insn ();
|
rtx last = emit_insn (gen_GOTaddr2picreg ());
|
rtx last = emit_insn (gen_GOTaddr2picreg ());
|
|
|
/* Mark these insns as possibly dead. Sometimes, flow2 may
|
/* Mark these insns as possibly dead. Sometimes, flow2 may
|
delete all uses of the PIC register. In this case, let it
|
delete all uses of the PIC register. In this case, let it
|
delete the initialization too. */
|
delete the initialization too. */
|
do
|
do
|
{
|
{
|
insn = NEXT_INSN (insn);
|
insn = NEXT_INSN (insn);
|
|
|
REG_NOTES (insn) = gen_rtx_EXPR_LIST (REG_MAYBE_DEAD,
|
REG_NOTES (insn) = gen_rtx_EXPR_LIST (REG_MAYBE_DEAD,
|
const0_rtx,
|
const0_rtx,
|
REG_NOTES (insn));
|
REG_NOTES (insn));
|
}
|
}
|
while (insn != last);
|
while (insn != last);
|
}
|
}
|
}
|
}
|
|
|
void
|
void
|
expand_epilogue (void)
|
expand_epilogue (void)
|
{
|
{
|
HOST_WIDE_INT size;
|
HOST_WIDE_INT size;
|
|
|
/* SIZE includes the fixed stack space needed for function calls. */
|
/* SIZE includes the fixed stack space needed for function calls. */
|
size = get_frame_size () + current_function_outgoing_args_size;
|
size = get_frame_size () + current_function_outgoing_args_size;
|
size += (current_function_outgoing_args_size ? 4 : 0);
|
size += (current_function_outgoing_args_size ? 4 : 0);
|
|
|
if (TARGET_AM33_2 && fp_regs_to_save ())
|
if (TARGET_AM33_2 && fp_regs_to_save ())
|
{
|
{
|
int num_regs_to_save = fp_regs_to_save (), i;
|
int num_regs_to_save = fp_regs_to_save (), i;
|
rtx reg = 0;
|
rtx reg = 0;
|
|
|
/* We have several options to restore FP registers. We could
|
/* We have several options to restore FP registers. We could
|
load them from SP offsets, but, if there are enough FP
|
load them from SP offsets, but, if there are enough FP
|
registers to restore, we win if we use a post-increment
|
registers to restore, we win if we use a post-increment
|
addressing mode. */
|
addressing mode. */
|
|
|
/* If we have a frame pointer, it's the best option, because we
|
/* If we have a frame pointer, it's the best option, because we
|
already know it has the value we want. */
|
already know it has the value we want. */
|
if (frame_pointer_needed)
|
if (frame_pointer_needed)
|
reg = gen_rtx_REG (SImode, FRAME_POINTER_REGNUM);
|
reg = gen_rtx_REG (SImode, FRAME_POINTER_REGNUM);
|
/* Otherwise, we may use `a1', since it's call-clobbered and
|
/* Otherwise, we may use `a1', since it's call-clobbered and
|
it's never used for return values. But only do so if it's
|
it's never used for return values. But only do so if it's
|
smaller than using SP offsets. */
|
smaller than using SP offsets. */
|
else
|
else
|
{
|
{
|
enum { restore_sp_post_adjust,
|
enum { restore_sp_post_adjust,
|
restore_sp_pre_adjust,
|
restore_sp_pre_adjust,
|
restore_sp_partial_adjust,
|
restore_sp_partial_adjust,
|
restore_a1 } strategy;
|
restore_a1 } strategy;
|
unsigned int this_strategy_size, strategy_size = (unsigned)-1;
|
unsigned int this_strategy_size, strategy_size = (unsigned)-1;
|
|
|
/* Consider using sp offsets before adjusting sp. */
|
/* Consider using sp offsets before adjusting sp. */
|
/* Insn: fmov (##,sp),fs#, for each fs# to be restored. */
|
/* Insn: fmov (##,sp),fs#, for each fs# to be restored. */
|
this_strategy_size = SIZE_FMOV_SP (size, num_regs_to_save);
|
this_strategy_size = SIZE_FMOV_SP (size, num_regs_to_save);
|
/* If size is too large, we'll have to adjust SP with an
|
/* If size is too large, we'll have to adjust SP with an
|
add. */
|
add. */
|
if (size + 4 * num_regs_to_save + REG_SAVE_BYTES > 255)
|
if (size + 4 * num_regs_to_save + REG_SAVE_BYTES > 255)
|
{
|
{
|
/* Insn: add size + 4 * num_regs_to_save, sp. */
|
/* Insn: add size + 4 * num_regs_to_save, sp. */
|
this_strategy_size += SIZE_ADD_SP (size + 4 * num_regs_to_save);
|
this_strategy_size += SIZE_ADD_SP (size + 4 * num_regs_to_save);
|
}
|
}
|
/* If we don't have to restore any non-FP registers,
|
/* If we don't have to restore any non-FP registers,
|
we'll be able to save one byte by using rets. */
|
we'll be able to save one byte by using rets. */
|
if (! REG_SAVE_BYTES)
|
if (! REG_SAVE_BYTES)
|
this_strategy_size--;
|
this_strategy_size--;
|
|
|
if (this_strategy_size < strategy_size)
|
if (this_strategy_size < strategy_size)
|
{
|
{
|
strategy = restore_sp_post_adjust;
|
strategy = restore_sp_post_adjust;
|
strategy_size = this_strategy_size;
|
strategy_size = this_strategy_size;
|
}
|
}
|
|
|
/* Consider using sp offsets after adjusting sp. */
|
/* Consider using sp offsets after adjusting sp. */
|
/* Insn: add size, sp. */
|
/* Insn: add size, sp. */
|
this_strategy_size = SIZE_ADD_SP (size);
|
this_strategy_size = SIZE_ADD_SP (size);
|
/* Insn: fmov (##,sp),fs#, for each fs# to be restored. */
|
/* Insn: fmov (##,sp),fs#, for each fs# to be restored. */
|
this_strategy_size += SIZE_FMOV_SP (0, num_regs_to_save);
|
this_strategy_size += SIZE_FMOV_SP (0, num_regs_to_save);
|
/* We're going to use ret to release the FP registers
|
/* We're going to use ret to release the FP registers
|
save area, so, no savings. */
|
save area, so, no savings. */
|
|
|
if (this_strategy_size < strategy_size)
|
if (this_strategy_size < strategy_size)
|
{
|
{
|
strategy = restore_sp_pre_adjust;
|
strategy = restore_sp_pre_adjust;
|
strategy_size = this_strategy_size;
|
strategy_size = this_strategy_size;
|
}
|
}
|
|
|
/* Consider using sp offsets after partially adjusting sp.
|
/* Consider using sp offsets after partially adjusting sp.
|
When size is close to 32Kb, we may be able to adjust SP
|
When size is close to 32Kb, we may be able to adjust SP
|
with an imm16 add instruction while still using fmov
|
with an imm16 add instruction while still using fmov
|
(d8,sp). */
|
(d8,sp). */
|
if (size + 4 * num_regs_to_save + REG_SAVE_BYTES > 255)
|
if (size + 4 * num_regs_to_save + REG_SAVE_BYTES > 255)
|
{
|
{
|
/* Insn: add size + 4 * num_regs_to_save
|
/* Insn: add size + 4 * num_regs_to_save
|
+ REG_SAVE_BYTES - 252,sp. */
|
+ REG_SAVE_BYTES - 252,sp. */
|
this_strategy_size = SIZE_ADD_SP (size + 4 * num_regs_to_save
|
this_strategy_size = SIZE_ADD_SP (size + 4 * num_regs_to_save
|
+ REG_SAVE_BYTES - 252);
|
+ REG_SAVE_BYTES - 252);
|
/* Insn: fmov (##,sp),fs#, fo each fs# to be restored. */
|
/* Insn: fmov (##,sp),fs#, fo each fs# to be restored. */
|
this_strategy_size += SIZE_FMOV_SP (252 - REG_SAVE_BYTES
|
this_strategy_size += SIZE_FMOV_SP (252 - REG_SAVE_BYTES
|
- 4 * num_regs_to_save,
|
- 4 * num_regs_to_save,
|
num_regs_to_save);
|
num_regs_to_save);
|
/* We're going to use ret to release the FP registers
|
/* We're going to use ret to release the FP registers
|
save area, so, no savings. */
|
save area, so, no savings. */
|
|
|
if (this_strategy_size < strategy_size)
|
if (this_strategy_size < strategy_size)
|
{
|
{
|
strategy = restore_sp_partial_adjust;
|
strategy = restore_sp_partial_adjust;
|
strategy_size = this_strategy_size;
|
strategy_size = this_strategy_size;
|
}
|
}
|
}
|
}
|
|
|
/* Consider using a1 in post-increment mode, as long as the
|
/* Consider using a1 in post-increment mode, as long as the
|
user hasn't changed the calling conventions of a1. */
|
user hasn't changed the calling conventions of a1. */
|
if (call_used_regs[FIRST_ADDRESS_REGNUM+1]
|
if (call_used_regs[FIRST_ADDRESS_REGNUM+1]
|
&& ! fixed_regs[FIRST_ADDRESS_REGNUM+1])
|
&& ! fixed_regs[FIRST_ADDRESS_REGNUM+1])
|
{
|
{
|
/* Insn: mov sp,a1. */
|
/* Insn: mov sp,a1. */
|
this_strategy_size = 1;
|
this_strategy_size = 1;
|
if (size)
|
if (size)
|
{
|
{
|
/* Insn: add size,a1. */
|
/* Insn: add size,a1. */
|
this_strategy_size += SIZE_ADD_AX (size);
|
this_strategy_size += SIZE_ADD_AX (size);
|
}
|
}
|
/* Insn: fmov (a1+),fs#, for each fs# to be restored. */
|
/* Insn: fmov (a1+),fs#, for each fs# to be restored. */
|
this_strategy_size += 3 * num_regs_to_save;
|
this_strategy_size += 3 * num_regs_to_save;
|
/* If size is large enough, we may be able to save a
|
/* If size is large enough, we may be able to save a
|
couple of bytes. */
|
couple of bytes. */
|
if (size + 4 * num_regs_to_save + REG_SAVE_BYTES > 255)
|
if (size + 4 * num_regs_to_save + REG_SAVE_BYTES > 255)
|
{
|
{
|
/* Insn: mov a1,sp. */
|
/* Insn: mov a1,sp. */
|
this_strategy_size += 2;
|
this_strategy_size += 2;
|
}
|
}
|
/* If we don't have to restore any non-FP registers,
|
/* If we don't have to restore any non-FP registers,
|
we'll be able to save one byte by using rets. */
|
we'll be able to save one byte by using rets. */
|
if (! REG_SAVE_BYTES)
|
if (! REG_SAVE_BYTES)
|
this_strategy_size--;
|
this_strategy_size--;
|
|
|
if (this_strategy_size < strategy_size)
|
if (this_strategy_size < strategy_size)
|
{
|
{
|
strategy = restore_a1;
|
strategy = restore_a1;
|
strategy_size = this_strategy_size;
|
strategy_size = this_strategy_size;
|
}
|
}
|
}
|
}
|
|
|
switch (strategy)
|
switch (strategy)
|
{
|
{
|
case restore_sp_post_adjust:
|
case restore_sp_post_adjust:
|
break;
|
break;
|
|
|
case restore_sp_pre_adjust:
|
case restore_sp_pre_adjust:
|
emit_insn (gen_addsi3 (stack_pointer_rtx,
|
emit_insn (gen_addsi3 (stack_pointer_rtx,
|
stack_pointer_rtx,
|
stack_pointer_rtx,
|
GEN_INT (size)));
|
GEN_INT (size)));
|
size = 0;
|
size = 0;
|
break;
|
break;
|
|
|
case restore_sp_partial_adjust:
|
case restore_sp_partial_adjust:
|
emit_insn (gen_addsi3 (stack_pointer_rtx,
|
emit_insn (gen_addsi3 (stack_pointer_rtx,
|
stack_pointer_rtx,
|
stack_pointer_rtx,
|
GEN_INT (size + 4 * num_regs_to_save
|
GEN_INT (size + 4 * num_regs_to_save
|
+ REG_SAVE_BYTES - 252)));
|
+ REG_SAVE_BYTES - 252)));
|
size = 252 - REG_SAVE_BYTES - 4 * num_regs_to_save;
|
size = 252 - REG_SAVE_BYTES - 4 * num_regs_to_save;
|
break;
|
break;
|
|
|
case restore_a1:
|
case restore_a1:
|
reg = gen_rtx_REG (SImode, FIRST_ADDRESS_REGNUM + 1);
|
reg = gen_rtx_REG (SImode, FIRST_ADDRESS_REGNUM + 1);
|
emit_insn (gen_movsi (reg, stack_pointer_rtx));
|
emit_insn (gen_movsi (reg, stack_pointer_rtx));
|
if (size)
|
if (size)
|
emit_insn (gen_addsi3 (reg, reg, GEN_INT (size)));
|
emit_insn (gen_addsi3 (reg, reg, GEN_INT (size)));
|
break;
|
break;
|
|
|
default:
|
default:
|
gcc_unreachable ();
|
gcc_unreachable ();
|
}
|
}
|
}
|
}
|
|
|
/* Adjust the selected register, if any, for post-increment. */
|
/* Adjust the selected register, if any, for post-increment. */
|
if (reg)
|
if (reg)
|
reg = gen_rtx_POST_INC (SImode, reg);
|
reg = gen_rtx_POST_INC (SImode, reg);
|
|
|
for (i = FIRST_FP_REGNUM; i <= LAST_FP_REGNUM; ++i)
|
for (i = FIRST_FP_REGNUM; i <= LAST_FP_REGNUM; ++i)
|
if (regs_ever_live[i] && ! call_used_regs[i])
|
if (regs_ever_live[i] && ! call_used_regs[i])
|
{
|
{
|
rtx addr;
|
rtx addr;
|
|
|
if (reg)
|
if (reg)
|
addr = reg;
|
addr = reg;
|
else if (size)
|
else if (size)
|
{
|
{
|
/* If we aren't using a post-increment register, use an
|
/* If we aren't using a post-increment register, use an
|
SP offset. */
|
SP offset. */
|
addr = gen_rtx_PLUS (SImode,
|
addr = gen_rtx_PLUS (SImode,
|
stack_pointer_rtx,
|
stack_pointer_rtx,
|
GEN_INT (size));
|
GEN_INT (size));
|
}
|
}
|
else
|
else
|
addr = stack_pointer_rtx;
|
addr = stack_pointer_rtx;
|
|
|
size += 4;
|
size += 4;
|
|
|
emit_insn (gen_movsi (gen_rtx_REG (SImode, i),
|
emit_insn (gen_movsi (gen_rtx_REG (SImode, i),
|
gen_rtx_MEM (SImode, addr)));
|
gen_rtx_MEM (SImode, addr)));
|
}
|
}
|
|
|
/* If we were using the restore_a1 strategy and the number of
|
/* If we were using the restore_a1 strategy and the number of
|
bytes to be released won't fit in the `ret' byte, copy `a1'
|
bytes to be released won't fit in the `ret' byte, copy `a1'
|
to `sp', to avoid having to use `add' to adjust it. */
|
to `sp', to avoid having to use `add' to adjust it. */
|
if (! frame_pointer_needed && reg && size + REG_SAVE_BYTES > 255)
|
if (! frame_pointer_needed && reg && size + REG_SAVE_BYTES > 255)
|
{
|
{
|
emit_move_insn (stack_pointer_rtx, XEXP (reg, 0));
|
emit_move_insn (stack_pointer_rtx, XEXP (reg, 0));
|
size = 0;
|
size = 0;
|
}
|
}
|
}
|
}
|
|
|
/* Maybe cut back the stack, except for the register save area.
|
/* Maybe cut back the stack, except for the register save area.
|
|
|
If the frame pointer exists, then use the frame pointer to
|
If the frame pointer exists, then use the frame pointer to
|
cut back the stack.
|
cut back the stack.
|
|
|
If the stack size + register save area is more than 255 bytes,
|
If the stack size + register save area is more than 255 bytes,
|
then the stack must be cut back here since the size + register
|
then the stack must be cut back here since the size + register
|
save size is too big for a ret/retf instruction.
|
save size is too big for a ret/retf instruction.
|
|
|
Else leave it alone, it will be cut back as part of the
|
Else leave it alone, it will be cut back as part of the
|
ret/retf instruction, or there wasn't any stack to begin with.
|
ret/retf instruction, or there wasn't any stack to begin with.
|
|
|
Under no circumstances should the register save area be
|
Under no circumstances should the register save area be
|
deallocated here, that would leave a window where an interrupt
|
deallocated here, that would leave a window where an interrupt
|
could occur and trash the register save area. */
|
could occur and trash the register save area. */
|
if (frame_pointer_needed)
|
if (frame_pointer_needed)
|
{
|
{
|
emit_move_insn (stack_pointer_rtx, frame_pointer_rtx);
|
emit_move_insn (stack_pointer_rtx, frame_pointer_rtx);
|
size = 0;
|
size = 0;
|
}
|
}
|
else if (size + REG_SAVE_BYTES > 255)
|
else if (size + REG_SAVE_BYTES > 255)
|
{
|
{
|
emit_insn (gen_addsi3 (stack_pointer_rtx,
|
emit_insn (gen_addsi3 (stack_pointer_rtx,
|
stack_pointer_rtx,
|
stack_pointer_rtx,
|
GEN_INT (size)));
|
GEN_INT (size)));
|
size = 0;
|
size = 0;
|
}
|
}
|
|
|
/* Adjust the stack and restore callee-saved registers, if any. */
|
/* Adjust the stack and restore callee-saved registers, if any. */
|
if (size || regs_ever_live[2] || regs_ever_live[3]
|
if (size || regs_ever_live[2] || regs_ever_live[3]
|
|| regs_ever_live[6] || regs_ever_live[7]
|
|| regs_ever_live[6] || regs_ever_live[7]
|
|| regs_ever_live[14] || regs_ever_live[15]
|
|| regs_ever_live[14] || regs_ever_live[15]
|
|| regs_ever_live[16] || regs_ever_live[17]
|
|| regs_ever_live[16] || regs_ever_live[17]
|
|| frame_pointer_needed)
|
|| frame_pointer_needed)
|
emit_jump_insn (gen_return_internal_regs
|
emit_jump_insn (gen_return_internal_regs
|
(GEN_INT (size + REG_SAVE_BYTES)));
|
(GEN_INT (size + REG_SAVE_BYTES)));
|
else
|
else
|
emit_jump_insn (gen_return_internal ());
|
emit_jump_insn (gen_return_internal ());
|
}
|
}
|
|
|
/* Update the condition code from the insn. */
|
/* Update the condition code from the insn. */
|
|
|
void
|
void
|
notice_update_cc (rtx body, rtx insn)
|
notice_update_cc (rtx body, rtx insn)
|
{
|
{
|
switch (get_attr_cc (insn))
|
switch (get_attr_cc (insn))
|
{
|
{
|
case CC_NONE:
|
case CC_NONE:
|
/* Insn does not affect CC at all. */
|
/* Insn does not affect CC at all. */
|
break;
|
break;
|
|
|
case CC_NONE_0HIT:
|
case CC_NONE_0HIT:
|
/* Insn does not change CC, but the 0'th operand has been changed. */
|
/* Insn does not change CC, but the 0'th operand has been changed. */
|
if (cc_status.value1 != 0
|
if (cc_status.value1 != 0
|
&& reg_overlap_mentioned_p (recog_data.operand[0], cc_status.value1))
|
&& reg_overlap_mentioned_p (recog_data.operand[0], cc_status.value1))
|
cc_status.value1 = 0;
|
cc_status.value1 = 0;
|
break;
|
break;
|
|
|
case CC_SET_ZN:
|
case CC_SET_ZN:
|
/* Insn sets the Z,N flags of CC to recog_data.operand[0].
|
/* Insn sets the Z,N flags of CC to recog_data.operand[0].
|
V,C are unusable. */
|
V,C are unusable. */
|
CC_STATUS_INIT;
|
CC_STATUS_INIT;
|
cc_status.flags |= CC_NO_CARRY | CC_OVERFLOW_UNUSABLE;
|
cc_status.flags |= CC_NO_CARRY | CC_OVERFLOW_UNUSABLE;
|
cc_status.value1 = recog_data.operand[0];
|
cc_status.value1 = recog_data.operand[0];
|
break;
|
break;
|
|
|
case CC_SET_ZNV:
|
case CC_SET_ZNV:
|
/* Insn sets the Z,N,V flags of CC to recog_data.operand[0].
|
/* Insn sets the Z,N,V flags of CC to recog_data.operand[0].
|
C is unusable. */
|
C is unusable. */
|
CC_STATUS_INIT;
|
CC_STATUS_INIT;
|
cc_status.flags |= CC_NO_CARRY;
|
cc_status.flags |= CC_NO_CARRY;
|
cc_status.value1 = recog_data.operand[0];
|
cc_status.value1 = recog_data.operand[0];
|
break;
|
break;
|
|
|
case CC_COMPARE:
|
case CC_COMPARE:
|
/* The insn is a compare instruction. */
|
/* The insn is a compare instruction. */
|
CC_STATUS_INIT;
|
CC_STATUS_INIT;
|
cc_status.value1 = SET_SRC (body);
|
cc_status.value1 = SET_SRC (body);
|
if (GET_CODE (cc_status.value1) == COMPARE
|
if (GET_CODE (cc_status.value1) == COMPARE
|
&& GET_MODE (XEXP (cc_status.value1, 0)) == SFmode)
|
&& GET_MODE (XEXP (cc_status.value1, 0)) == SFmode)
|
cc_status.mdep.fpCC = 1;
|
cc_status.mdep.fpCC = 1;
|
break;
|
break;
|
|
|
case CC_CLOBBER:
|
case CC_CLOBBER:
|
/* Insn doesn't leave CC in a usable state. */
|
/* Insn doesn't leave CC in a usable state. */
|
CC_STATUS_INIT;
|
CC_STATUS_INIT;
|
break;
|
break;
|
|
|
default:
|
default:
|
gcc_unreachable ();
|
gcc_unreachable ();
|
}
|
}
|
}
|
}
|
|
|
/* Recognize the PARALLEL rtx generated by mn10300_gen_multiple_store().
|
/* Recognize the PARALLEL rtx generated by mn10300_gen_multiple_store().
|
This function is for MATCH_PARALLEL and so assumes OP is known to be
|
This function is for MATCH_PARALLEL and so assumes OP is known to be
|
parallel. If OP is a multiple store, return a mask indicating which
|
parallel. If OP is a multiple store, return a mask indicating which
|
registers it saves. Return 0 otherwise. */
|
registers it saves. Return 0 otherwise. */
|
|
|
int
|
int
|
store_multiple_operation (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED)
|
store_multiple_operation (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED)
|
{
|
{
|
int count;
|
int count;
|
int mask;
|
int mask;
|
int i;
|
int i;
|
unsigned int last;
|
unsigned int last;
|
rtx elt;
|
rtx elt;
|
|
|
count = XVECLEN (op, 0);
|
count = XVECLEN (op, 0);
|
if (count < 2)
|
if (count < 2)
|
return 0;
|
return 0;
|
|
|
/* Check that first instruction has the form (set (sp) (plus A B)) */
|
/* Check that first instruction has the form (set (sp) (plus A B)) */
|
elt = XVECEXP (op, 0, 0);
|
elt = XVECEXP (op, 0, 0);
|
if (GET_CODE (elt) != SET
|
if (GET_CODE (elt) != SET
|
|| GET_CODE (SET_DEST (elt)) != REG
|
|| GET_CODE (SET_DEST (elt)) != REG
|
|| REGNO (SET_DEST (elt)) != STACK_POINTER_REGNUM
|
|| REGNO (SET_DEST (elt)) != STACK_POINTER_REGNUM
|
|| GET_CODE (SET_SRC (elt)) != PLUS)
|
|| GET_CODE (SET_SRC (elt)) != PLUS)
|
return 0;
|
return 0;
|
|
|
/* Check that A is the stack pointer and B is the expected stack size.
|
/* Check that A is the stack pointer and B is the expected stack size.
|
For OP to match, each subsequent instruction should push a word onto
|
For OP to match, each subsequent instruction should push a word onto
|
the stack. We therefore expect the first instruction to create
|
the stack. We therefore expect the first instruction to create
|
COUNT-1 stack slots. */
|
COUNT-1 stack slots. */
|
elt = SET_SRC (elt);
|
elt = SET_SRC (elt);
|
if (GET_CODE (XEXP (elt, 0)) != REG
|
if (GET_CODE (XEXP (elt, 0)) != REG
|
|| REGNO (XEXP (elt, 0)) != STACK_POINTER_REGNUM
|
|| REGNO (XEXP (elt, 0)) != STACK_POINTER_REGNUM
|
|| GET_CODE (XEXP (elt, 1)) != CONST_INT
|
|| GET_CODE (XEXP (elt, 1)) != CONST_INT
|
|| INTVAL (XEXP (elt, 1)) != -(count - 1) * 4)
|
|| INTVAL (XEXP (elt, 1)) != -(count - 1) * 4)
|
return 0;
|
return 0;
|
|
|
/* Now go through the rest of the vector elements. They must be
|
/* Now go through the rest of the vector elements. They must be
|
ordered so that the first instruction stores the highest-numbered
|
ordered so that the first instruction stores the highest-numbered
|
register to the highest stack slot and that subsequent instructions
|
register to the highest stack slot and that subsequent instructions
|
store a lower-numbered register to the slot below.
|
store a lower-numbered register to the slot below.
|
|
|
LAST keeps track of the smallest-numbered register stored so far.
|
LAST keeps track of the smallest-numbered register stored so far.
|
MASK is the set of stored registers. */
|
MASK is the set of stored registers. */
|
last = LAST_EXTENDED_REGNUM + 1;
|
last = LAST_EXTENDED_REGNUM + 1;
|
mask = 0;
|
mask = 0;
|
for (i = 1; i < count; i++)
|
for (i = 1; i < count; i++)
|
{
|
{
|
/* Check that element i is a (set (mem M) R) and that R is valid. */
|
/* Check that element i is a (set (mem M) R) and that R is valid. */
|
elt = XVECEXP (op, 0, i);
|
elt = XVECEXP (op, 0, i);
|
if (GET_CODE (elt) != SET
|
if (GET_CODE (elt) != SET
|
|| GET_CODE (SET_DEST (elt)) != MEM
|
|| GET_CODE (SET_DEST (elt)) != MEM
|
|| GET_CODE (SET_SRC (elt)) != REG
|
|| GET_CODE (SET_SRC (elt)) != REG
|
|| REGNO (SET_SRC (elt)) >= last)
|
|| REGNO (SET_SRC (elt)) >= last)
|
return 0;
|
return 0;
|
|
|
/* R was OK, so provisionally add it to MASK. We return 0 in any
|
/* R was OK, so provisionally add it to MASK. We return 0 in any
|
case if the rest of the instruction has a flaw. */
|
case if the rest of the instruction has a flaw. */
|
last = REGNO (SET_SRC (elt));
|
last = REGNO (SET_SRC (elt));
|
mask |= (1 << last);
|
mask |= (1 << last);
|
|
|
/* Check that M has the form (plus (sp) (const_int -I*4)) */
|
/* Check that M has the form (plus (sp) (const_int -I*4)) */
|
elt = XEXP (SET_DEST (elt), 0);
|
elt = XEXP (SET_DEST (elt), 0);
|
if (GET_CODE (elt) != PLUS
|
if (GET_CODE (elt) != PLUS
|
|| GET_CODE (XEXP (elt, 0)) != REG
|
|| GET_CODE (XEXP (elt, 0)) != REG
|
|| REGNO (XEXP (elt, 0)) != STACK_POINTER_REGNUM
|
|| REGNO (XEXP (elt, 0)) != STACK_POINTER_REGNUM
|
|| GET_CODE (XEXP (elt, 1)) != CONST_INT
|
|| GET_CODE (XEXP (elt, 1)) != CONST_INT
|
|| INTVAL (XEXP (elt, 1)) != -i * 4)
|
|| INTVAL (XEXP (elt, 1)) != -i * 4)
|
return 0;
|
return 0;
|
}
|
}
|
|
|
/* All or none of the callee-saved extended registers must be in the set. */
|
/* All or none of the callee-saved extended registers must be in the set. */
|
if ((mask & 0x3c000) != 0
|
if ((mask & 0x3c000) != 0
|
&& (mask & 0x3c000) != 0x3c000)
|
&& (mask & 0x3c000) != 0x3c000)
|
return 0;
|
return 0;
|
|
|
return mask;
|
return mask;
|
}
|
}
|
|
|
/* What (if any) secondary registers are needed to move IN with mode
|
/* What (if any) secondary registers are needed to move IN with mode
|
MODE into a register in register class CLASS.
|
MODE into a register in register class CLASS.
|
|
|
We might be able to simplify this. */
|
We might be able to simplify this. */
|
enum reg_class
|
enum reg_class
|
mn10300_secondary_reload_class (enum reg_class class, enum machine_mode mode,
|
mn10300_secondary_reload_class (enum reg_class class, enum machine_mode mode,
|
rtx in)
|
rtx in)
|
{
|
{
|
/* Memory loads less than a full word wide can't have an
|
/* Memory loads less than a full word wide can't have an
|
address or stack pointer destination. They must use
|
address or stack pointer destination. They must use
|
a data register as an intermediate register. */
|
a data register as an intermediate register. */
|
if ((GET_CODE (in) == MEM
|
if ((GET_CODE (in) == MEM
|
|| (GET_CODE (in) == REG
|
|| (GET_CODE (in) == REG
|
&& REGNO (in) >= FIRST_PSEUDO_REGISTER)
|
&& REGNO (in) >= FIRST_PSEUDO_REGISTER)
|
|| (GET_CODE (in) == SUBREG
|
|| (GET_CODE (in) == SUBREG
|
&& GET_CODE (SUBREG_REG (in)) == REG
|
&& GET_CODE (SUBREG_REG (in)) == REG
|
&& REGNO (SUBREG_REG (in)) >= FIRST_PSEUDO_REGISTER))
|
&& REGNO (SUBREG_REG (in)) >= FIRST_PSEUDO_REGISTER))
|
&& (mode == QImode || mode == HImode)
|
&& (mode == QImode || mode == HImode)
|
&& (class == ADDRESS_REGS || class == SP_REGS
|
&& (class == ADDRESS_REGS || class == SP_REGS
|
|| class == SP_OR_ADDRESS_REGS))
|
|| class == SP_OR_ADDRESS_REGS))
|
{
|
{
|
if (TARGET_AM33)
|
if (TARGET_AM33)
|
return DATA_OR_EXTENDED_REGS;
|
return DATA_OR_EXTENDED_REGS;
|
return DATA_REGS;
|
return DATA_REGS;
|
}
|
}
|
|
|
/* We can't directly load sp + const_int into a data register;
|
/* We can't directly load sp + const_int into a data register;
|
we must use an address register as an intermediate. */
|
we must use an address register as an intermediate. */
|
if (class != SP_REGS
|
if (class != SP_REGS
|
&& class != ADDRESS_REGS
|
&& class != ADDRESS_REGS
|
&& class != SP_OR_ADDRESS_REGS
|
&& class != SP_OR_ADDRESS_REGS
|
&& class != SP_OR_EXTENDED_REGS
|
&& class != SP_OR_EXTENDED_REGS
|
&& class != ADDRESS_OR_EXTENDED_REGS
|
&& class != ADDRESS_OR_EXTENDED_REGS
|
&& class != SP_OR_ADDRESS_OR_EXTENDED_REGS
|
&& class != SP_OR_ADDRESS_OR_EXTENDED_REGS
|
&& (in == stack_pointer_rtx
|
&& (in == stack_pointer_rtx
|
|| (GET_CODE (in) == PLUS
|
|| (GET_CODE (in) == PLUS
|
&& (XEXP (in, 0) == stack_pointer_rtx
|
&& (XEXP (in, 0) == stack_pointer_rtx
|
|| XEXP (in, 1) == stack_pointer_rtx))))
|
|| XEXP (in, 1) == stack_pointer_rtx))))
|
return ADDRESS_REGS;
|
return ADDRESS_REGS;
|
|
|
if (GET_CODE (in) == PLUS
|
if (GET_CODE (in) == PLUS
|
&& (XEXP (in, 0) == stack_pointer_rtx
|
&& (XEXP (in, 0) == stack_pointer_rtx
|
|| XEXP (in, 1) == stack_pointer_rtx))
|
|| XEXP (in, 1) == stack_pointer_rtx))
|
{
|
{
|
if (TARGET_AM33)
|
if (TARGET_AM33)
|
return DATA_OR_EXTENDED_REGS;
|
return DATA_OR_EXTENDED_REGS;
|
return DATA_REGS;
|
return DATA_REGS;
|
}
|
}
|
|
|
if (TARGET_AM33_2 && class == FP_REGS
|
if (TARGET_AM33_2 && class == FP_REGS
|
&& GET_CODE (in) == MEM && ! OK_FOR_Q (in))
|
&& GET_CODE (in) == MEM && ! OK_FOR_Q (in))
|
{
|
{
|
if (TARGET_AM33)
|
if (TARGET_AM33)
|
return DATA_OR_EXTENDED_REGS;
|
return DATA_OR_EXTENDED_REGS;
|
return DATA_REGS;
|
return DATA_REGS;
|
}
|
}
|
|
|
/* Otherwise assume no secondary reloads are needed. */
|
/* Otherwise assume no secondary reloads are needed. */
|
return NO_REGS;
|
return NO_REGS;
|
}
|
}
|
|
|
int
|
int
|
initial_offset (int from, int to)
|
initial_offset (int from, int to)
|
{
|
{
|
/* The difference between the argument pointer and the frame pointer
|
/* The difference between the argument pointer and the frame pointer
|
is the size of the callee register save area. */
|
is the size of the callee register save area. */
|
if (from == ARG_POINTER_REGNUM && to == FRAME_POINTER_REGNUM)
|
if (from == ARG_POINTER_REGNUM && to == FRAME_POINTER_REGNUM)
|
{
|
{
|
if (regs_ever_live[2] || regs_ever_live[3]
|
if (regs_ever_live[2] || regs_ever_live[3]
|
|| regs_ever_live[6] || regs_ever_live[7]
|
|| regs_ever_live[6] || regs_ever_live[7]
|
|| regs_ever_live[14] || regs_ever_live[15]
|
|| regs_ever_live[14] || regs_ever_live[15]
|
|| regs_ever_live[16] || regs_ever_live[17]
|
|| regs_ever_live[16] || regs_ever_live[17]
|
|| fp_regs_to_save ()
|
|| fp_regs_to_save ()
|
|| frame_pointer_needed)
|
|| frame_pointer_needed)
|
return REG_SAVE_BYTES
|
return REG_SAVE_BYTES
|
+ 4 * fp_regs_to_save ();
|
+ 4 * fp_regs_to_save ();
|
else
|
else
|
return 0;
|
return 0;
|
}
|
}
|
|
|
/* The difference between the argument pointer and the stack pointer is
|
/* The difference between the argument pointer and the stack pointer is
|
the sum of the size of this function's frame, the callee register save
|
the sum of the size of this function's frame, the callee register save
|
area, and the fixed stack space needed for function calls (if any). */
|
area, and the fixed stack space needed for function calls (if any). */
|
if (from == ARG_POINTER_REGNUM && to == STACK_POINTER_REGNUM)
|
if (from == ARG_POINTER_REGNUM && to == STACK_POINTER_REGNUM)
|
{
|
{
|
if (regs_ever_live[2] || regs_ever_live[3]
|
if (regs_ever_live[2] || regs_ever_live[3]
|
|| regs_ever_live[6] || regs_ever_live[7]
|
|| regs_ever_live[6] || regs_ever_live[7]
|
|| regs_ever_live[14] || regs_ever_live[15]
|
|| regs_ever_live[14] || regs_ever_live[15]
|
|| regs_ever_live[16] || regs_ever_live[17]
|
|| regs_ever_live[16] || regs_ever_live[17]
|
|| fp_regs_to_save ()
|
|| fp_regs_to_save ()
|
|| frame_pointer_needed)
|
|| frame_pointer_needed)
|
return (get_frame_size () + REG_SAVE_BYTES
|
return (get_frame_size () + REG_SAVE_BYTES
|
+ 4 * fp_regs_to_save ()
|
+ 4 * fp_regs_to_save ()
|
+ (current_function_outgoing_args_size
|
+ (current_function_outgoing_args_size
|
? current_function_outgoing_args_size + 4 : 0));
|
? current_function_outgoing_args_size + 4 : 0));
|
else
|
else
|
return (get_frame_size ()
|
return (get_frame_size ()
|
+ (current_function_outgoing_args_size
|
+ (current_function_outgoing_args_size
|
? current_function_outgoing_args_size + 4 : 0));
|
? current_function_outgoing_args_size + 4 : 0));
|
}
|
}
|
|
|
/* The difference between the frame pointer and stack pointer is the sum
|
/* The difference between the frame pointer and stack pointer is the sum
|
of the size of this function's frame and the fixed stack space needed
|
of the size of this function's frame and the fixed stack space needed
|
for function calls (if any). */
|
for function calls (if any). */
|
if (from == FRAME_POINTER_REGNUM && to == STACK_POINTER_REGNUM)
|
if (from == FRAME_POINTER_REGNUM && to == STACK_POINTER_REGNUM)
|
return (get_frame_size ()
|
return (get_frame_size ()
|
+ (current_function_outgoing_args_size
|
+ (current_function_outgoing_args_size
|
? current_function_outgoing_args_size + 4 : 0));
|
? current_function_outgoing_args_size + 4 : 0));
|
|
|
gcc_unreachable ();
|
gcc_unreachable ();
|
}
|
}
|
|
|
/* Worker function for TARGET_RETURN_IN_MEMORY. */
|
/* Worker function for TARGET_RETURN_IN_MEMORY. */
|
|
|
static bool
|
static bool
|
mn10300_return_in_memory (tree type, tree fntype ATTRIBUTE_UNUSED)
|
mn10300_return_in_memory (tree type, tree fntype ATTRIBUTE_UNUSED)
|
{
|
{
|
/* Return values > 8 bytes in length in memory. */
|
/* Return values > 8 bytes in length in memory. */
|
return (int_size_in_bytes (type) > 8
|
return (int_size_in_bytes (type) > 8
|
|| int_size_in_bytes (type) == 0
|
|| int_size_in_bytes (type) == 0
|
|| TYPE_MODE (type) == BLKmode);
|
|| TYPE_MODE (type) == BLKmode);
|
}
|
}
|
|
|
/* Flush the argument registers to the stack for a stdarg function;
|
/* Flush the argument registers to the stack for a stdarg function;
|
return the new argument pointer. */
|
return the new argument pointer. */
|
static rtx
|
static rtx
|
mn10300_builtin_saveregs (void)
|
mn10300_builtin_saveregs (void)
|
{
|
{
|
rtx offset, mem;
|
rtx offset, mem;
|
tree fntype = TREE_TYPE (current_function_decl);
|
tree fntype = TREE_TYPE (current_function_decl);
|
int argadj = ((!(TYPE_ARG_TYPES (fntype) != 0
|
int argadj = ((!(TYPE_ARG_TYPES (fntype) != 0
|
&& (TREE_VALUE (tree_last (TYPE_ARG_TYPES (fntype)))
|
&& (TREE_VALUE (tree_last (TYPE_ARG_TYPES (fntype)))
|
!= void_type_node)))
|
!= void_type_node)))
|
? UNITS_PER_WORD : 0);
|
? UNITS_PER_WORD : 0);
|
int set = get_varargs_alias_set ();
|
int set = get_varargs_alias_set ();
|
|
|
if (argadj)
|
if (argadj)
|
offset = plus_constant (current_function_arg_offset_rtx, argadj);
|
offset = plus_constant (current_function_arg_offset_rtx, argadj);
|
else
|
else
|
offset = current_function_arg_offset_rtx;
|
offset = current_function_arg_offset_rtx;
|
|
|
mem = gen_rtx_MEM (SImode, current_function_internal_arg_pointer);
|
mem = gen_rtx_MEM (SImode, current_function_internal_arg_pointer);
|
set_mem_alias_set (mem, set);
|
set_mem_alias_set (mem, set);
|
emit_move_insn (mem, gen_rtx_REG (SImode, 0));
|
emit_move_insn (mem, gen_rtx_REG (SImode, 0));
|
|
|
mem = gen_rtx_MEM (SImode,
|
mem = gen_rtx_MEM (SImode,
|
plus_constant (current_function_internal_arg_pointer, 4));
|
plus_constant (current_function_internal_arg_pointer, 4));
|
set_mem_alias_set (mem, set);
|
set_mem_alias_set (mem, set);
|
emit_move_insn (mem, gen_rtx_REG (SImode, 1));
|
emit_move_insn (mem, gen_rtx_REG (SImode, 1));
|
|
|
return copy_to_reg (expand_binop (Pmode, add_optab,
|
return copy_to_reg (expand_binop (Pmode, add_optab,
|
current_function_internal_arg_pointer,
|
current_function_internal_arg_pointer,
|
offset, 0, 0, OPTAB_LIB_WIDEN));
|
offset, 0, 0, OPTAB_LIB_WIDEN));
|
}
|
}
|
|
|
void
|
void
|
mn10300_va_start (tree valist, rtx nextarg)
|
mn10300_va_start (tree valist, rtx nextarg)
|
{
|
{
|
nextarg = expand_builtin_saveregs ();
|
nextarg = expand_builtin_saveregs ();
|
std_expand_builtin_va_start (valist, nextarg);
|
std_expand_builtin_va_start (valist, nextarg);
|
}
|
}
|
|
|
/* Return true when a parameter should be passed by reference. */
|
/* Return true when a parameter should be passed by reference. */
|
|
|
static bool
|
static bool
|
mn10300_pass_by_reference (CUMULATIVE_ARGS *cum ATTRIBUTE_UNUSED,
|
mn10300_pass_by_reference (CUMULATIVE_ARGS *cum ATTRIBUTE_UNUSED,
|
enum machine_mode mode, tree type,
|
enum machine_mode mode, tree type,
|
bool named ATTRIBUTE_UNUSED)
|
bool named ATTRIBUTE_UNUSED)
|
{
|
{
|
unsigned HOST_WIDE_INT size;
|
unsigned HOST_WIDE_INT size;
|
|
|
if (type)
|
if (type)
|
size = int_size_in_bytes (type);
|
size = int_size_in_bytes (type);
|
else
|
else
|
size = GET_MODE_SIZE (mode);
|
size = GET_MODE_SIZE (mode);
|
|
|
return (size > 8 || size == 0);
|
return (size > 8 || size == 0);
|
}
|
}
|
|
|
/* Return an RTX to represent where a value with mode MODE will be returned
|
/* Return an RTX to represent where a value with mode MODE will be returned
|
from a function. If the result is 0, the argument is pushed. */
|
from a function. If the result is 0, the argument is pushed. */
|
|
|
rtx
|
rtx
|
function_arg (CUMULATIVE_ARGS *cum, enum machine_mode mode,
|
function_arg (CUMULATIVE_ARGS *cum, enum machine_mode mode,
|
tree type, int named ATTRIBUTE_UNUSED)
|
tree type, int named ATTRIBUTE_UNUSED)
|
{
|
{
|
rtx result = 0;
|
rtx result = 0;
|
int size, align;
|
int size, align;
|
|
|
/* We only support using 2 data registers as argument registers. */
|
/* We only support using 2 data registers as argument registers. */
|
int nregs = 2;
|
int nregs = 2;
|
|
|
/* Figure out the size of the object to be passed. */
|
/* Figure out the size of the object to be passed. */
|
if (mode == BLKmode)
|
if (mode == BLKmode)
|
size = int_size_in_bytes (type);
|
size = int_size_in_bytes (type);
|
else
|
else
|
size = GET_MODE_SIZE (mode);
|
size = GET_MODE_SIZE (mode);
|
|
|
/* Figure out the alignment of the object to be passed. */
|
/* Figure out the alignment of the object to be passed. */
|
align = size;
|
align = size;
|
|
|
cum->nbytes = (cum->nbytes + 3) & ~3;
|
cum->nbytes = (cum->nbytes + 3) & ~3;
|
|
|
/* Don't pass this arg via a register if all the argument registers
|
/* Don't pass this arg via a register if all the argument registers
|
are used up. */
|
are used up. */
|
if (cum->nbytes > nregs * UNITS_PER_WORD)
|
if (cum->nbytes > nregs * UNITS_PER_WORD)
|
return 0;
|
return 0;
|
|
|
/* Don't pass this arg via a register if it would be split between
|
/* Don't pass this arg via a register if it would be split between
|
registers and memory. */
|
registers and memory. */
|
if (type == NULL_TREE
|
if (type == NULL_TREE
|
&& cum->nbytes + size > nregs * UNITS_PER_WORD)
|
&& cum->nbytes + size > nregs * UNITS_PER_WORD)
|
return 0;
|
return 0;
|
|
|
switch (cum->nbytes / UNITS_PER_WORD)
|
switch (cum->nbytes / UNITS_PER_WORD)
|
{
|
{
|
case 0:
|
case 0:
|
result = gen_rtx_REG (mode, 0);
|
result = gen_rtx_REG (mode, 0);
|
break;
|
break;
|
case 1:
|
case 1:
|
result = gen_rtx_REG (mode, 1);
|
result = gen_rtx_REG (mode, 1);
|
break;
|
break;
|
default:
|
default:
|
result = 0;
|
result = 0;
|
}
|
}
|
|
|
return result;
|
return result;
|
}
|
}
|
|
|
/* Return the number of bytes of registers to use for an argument passed
|
/* Return the number of bytes of registers to use for an argument passed
|
partially in registers and partially in memory. */
|
partially in registers and partially in memory. */
|
|
|
static int
|
static int
|
mn10300_arg_partial_bytes (CUMULATIVE_ARGS *cum, enum machine_mode mode,
|
mn10300_arg_partial_bytes (CUMULATIVE_ARGS *cum, enum machine_mode mode,
|
tree type, bool named ATTRIBUTE_UNUSED)
|
tree type, bool named ATTRIBUTE_UNUSED)
|
{
|
{
|
int size, align;
|
int size, align;
|
|
|
/* We only support using 2 data registers as argument registers. */
|
/* We only support using 2 data registers as argument registers. */
|
int nregs = 2;
|
int nregs = 2;
|
|
|
/* Figure out the size of the object to be passed. */
|
/* Figure out the size of the object to be passed. */
|
if (mode == BLKmode)
|
if (mode == BLKmode)
|
size = int_size_in_bytes (type);
|
size = int_size_in_bytes (type);
|
else
|
else
|
size = GET_MODE_SIZE (mode);
|
size = GET_MODE_SIZE (mode);
|
|
|
/* Figure out the alignment of the object to be passed. */
|
/* Figure out the alignment of the object to be passed. */
|
align = size;
|
align = size;
|
|
|
cum->nbytes = (cum->nbytes + 3) & ~3;
|
cum->nbytes = (cum->nbytes + 3) & ~3;
|
|
|
/* Don't pass this arg via a register if all the argument registers
|
/* Don't pass this arg via a register if all the argument registers
|
are used up. */
|
are used up. */
|
if (cum->nbytes > nregs * UNITS_PER_WORD)
|
if (cum->nbytes > nregs * UNITS_PER_WORD)
|
return 0;
|
return 0;
|
|
|
if (cum->nbytes + size <= nregs * UNITS_PER_WORD)
|
if (cum->nbytes + size <= nregs * UNITS_PER_WORD)
|
return 0;
|
return 0;
|
|
|
/* Don't pass this arg via a register if it would be split between
|
/* Don't pass this arg via a register if it would be split between
|
registers and memory. */
|
registers and memory. */
|
if (type == NULL_TREE
|
if (type == NULL_TREE
|
&& cum->nbytes + size > nregs * UNITS_PER_WORD)
|
&& cum->nbytes + size > nregs * UNITS_PER_WORD)
|
return 0;
|
return 0;
|
|
|
return nregs * UNITS_PER_WORD - cum->nbytes;
|
return nregs * UNITS_PER_WORD - cum->nbytes;
|
}
|
}
|
|
|
/* Return the location of the function's value. This will be either
|
/* Return the location of the function's value. This will be either
|
$d0 for integer functions, $a0 for pointers, or a PARALLEL of both
|
$d0 for integer functions, $a0 for pointers, or a PARALLEL of both
|
$d0 and $a0 if the -mreturn-pointer-on-do flag is set. Note that
|
$d0 and $a0 if the -mreturn-pointer-on-do flag is set. Note that
|
we only return the PARALLEL for outgoing values; we do not want
|
we only return the PARALLEL for outgoing values; we do not want
|
callers relying on this extra copy. */
|
callers relying on this extra copy. */
|
|
|
rtx
|
rtx
|
mn10300_function_value (tree valtype, tree func, int outgoing)
|
mn10300_function_value (tree valtype, tree func, int outgoing)
|
{
|
{
|
rtx rv;
|
rtx rv;
|
enum machine_mode mode = TYPE_MODE (valtype);
|
enum machine_mode mode = TYPE_MODE (valtype);
|
|
|
if (! POINTER_TYPE_P (valtype))
|
if (! POINTER_TYPE_P (valtype))
|
return gen_rtx_REG (mode, FIRST_DATA_REGNUM);
|
return gen_rtx_REG (mode, FIRST_DATA_REGNUM);
|
else if (! TARGET_PTR_A0D0 || ! outgoing
|
else if (! TARGET_PTR_A0D0 || ! outgoing
|
|| current_function_returns_struct)
|
|| current_function_returns_struct)
|
return gen_rtx_REG (mode, FIRST_ADDRESS_REGNUM);
|
return gen_rtx_REG (mode, FIRST_ADDRESS_REGNUM);
|
|
|
rv = gen_rtx_PARALLEL (mode, rtvec_alloc (2));
|
rv = gen_rtx_PARALLEL (mode, rtvec_alloc (2));
|
XVECEXP (rv, 0, 0)
|
XVECEXP (rv, 0, 0)
|
= gen_rtx_EXPR_LIST (VOIDmode,
|
= gen_rtx_EXPR_LIST (VOIDmode,
|
gen_rtx_REG (mode, FIRST_ADDRESS_REGNUM),
|
gen_rtx_REG (mode, FIRST_ADDRESS_REGNUM),
|
GEN_INT (0));
|
GEN_INT (0));
|
|
|
XVECEXP (rv, 0, 1)
|
XVECEXP (rv, 0, 1)
|
= gen_rtx_EXPR_LIST (VOIDmode,
|
= gen_rtx_EXPR_LIST (VOIDmode,
|
gen_rtx_REG (mode, FIRST_DATA_REGNUM),
|
gen_rtx_REG (mode, FIRST_DATA_REGNUM),
|
GEN_INT (0));
|
GEN_INT (0));
|
return rv;
|
return rv;
|
}
|
}
|
|
|
/* Output a tst insn. */
|
/* Output a tst insn. */
|
const char *
|
const char *
|
output_tst (rtx operand, rtx insn)
|
output_tst (rtx operand, rtx insn)
|
{
|
{
|
rtx temp;
|
rtx temp;
|
int past_call = 0;
|
int past_call = 0;
|
|
|
/* We can save a byte if we can find a register which has the value
|
/* We can save a byte if we can find a register which has the value
|
zero in it. */
|
zero in it. */
|
temp = PREV_INSN (insn);
|
temp = PREV_INSN (insn);
|
while (optimize && temp)
|
while (optimize && temp)
|
{
|
{
|
rtx set;
|
rtx set;
|
|
|
/* We allow the search to go through call insns. We record
|
/* We allow the search to go through call insns. We record
|
the fact that we've past a CALL_INSN and reject matches which
|
the fact that we've past a CALL_INSN and reject matches which
|
use call clobbered registers. */
|
use call clobbered registers. */
|
if (GET_CODE (temp) == CODE_LABEL
|
if (GET_CODE (temp) == CODE_LABEL
|
|| GET_CODE (temp) == JUMP_INSN
|
|| GET_CODE (temp) == JUMP_INSN
|
|| GET_CODE (temp) == BARRIER)
|
|| GET_CODE (temp) == BARRIER)
|
break;
|
break;
|
|
|
if (GET_CODE (temp) == CALL_INSN)
|
if (GET_CODE (temp) == CALL_INSN)
|
past_call = 1;
|
past_call = 1;
|
|
|
if (GET_CODE (temp) == NOTE)
|
if (GET_CODE (temp) == NOTE)
|
{
|
{
|
temp = PREV_INSN (temp);
|
temp = PREV_INSN (temp);
|
continue;
|
continue;
|
}
|
}
|
|
|
/* It must be an insn, see if it is a simple set. */
|
/* It must be an insn, see if it is a simple set. */
|
set = single_set (temp);
|
set = single_set (temp);
|
if (!set)
|
if (!set)
|
{
|
{
|
temp = PREV_INSN (temp);
|
temp = PREV_INSN (temp);
|
continue;
|
continue;
|
}
|
}
|
|
|
/* Are we setting a data register to zero (this does not win for
|
/* Are we setting a data register to zero (this does not win for
|
address registers)?
|
address registers)?
|
|
|
If it's a call clobbered register, have we past a call?
|
If it's a call clobbered register, have we past a call?
|
|
|
Make sure the register we find isn't the same as ourself;
|
Make sure the register we find isn't the same as ourself;
|
the mn10300 can't encode that.
|
the mn10300 can't encode that.
|
|
|
??? reg_set_between_p return nonzero anytime we pass a CALL_INSN
|
??? reg_set_between_p return nonzero anytime we pass a CALL_INSN
|
so the code to detect calls here isn't doing anything useful. */
|
so the code to detect calls here isn't doing anything useful. */
|
if (REG_P (SET_DEST (set))
|
if (REG_P (SET_DEST (set))
|
&& SET_SRC (set) == CONST0_RTX (GET_MODE (SET_DEST (set)))
|
&& SET_SRC (set) == CONST0_RTX (GET_MODE (SET_DEST (set)))
|
&& !reg_set_between_p (SET_DEST (set), temp, insn)
|
&& !reg_set_between_p (SET_DEST (set), temp, insn)
|
&& (REGNO_REG_CLASS (REGNO (SET_DEST (set)))
|
&& (REGNO_REG_CLASS (REGNO (SET_DEST (set)))
|
== REGNO_REG_CLASS (REGNO (operand)))
|
== REGNO_REG_CLASS (REGNO (operand)))
|
&& REGNO_REG_CLASS (REGNO (SET_DEST (set))) != EXTENDED_REGS
|
&& REGNO_REG_CLASS (REGNO (SET_DEST (set))) != EXTENDED_REGS
|
&& REGNO (SET_DEST (set)) != REGNO (operand)
|
&& REGNO (SET_DEST (set)) != REGNO (operand)
|
&& (!past_call
|
&& (!past_call
|
|| !call_used_regs[REGNO (SET_DEST (set))]))
|
|| !call_used_regs[REGNO (SET_DEST (set))]))
|
{
|
{
|
rtx xoperands[2];
|
rtx xoperands[2];
|
xoperands[0] = operand;
|
xoperands[0] = operand;
|
xoperands[1] = SET_DEST (set);
|
xoperands[1] = SET_DEST (set);
|
|
|
output_asm_insn ("cmp %1,%0", xoperands);
|
output_asm_insn ("cmp %1,%0", xoperands);
|
return "";
|
return "";
|
}
|
}
|
|
|
if (REGNO_REG_CLASS (REGNO (operand)) == EXTENDED_REGS
|
if (REGNO_REG_CLASS (REGNO (operand)) == EXTENDED_REGS
|
&& REG_P (SET_DEST (set))
|
&& REG_P (SET_DEST (set))
|
&& SET_SRC (set) == CONST0_RTX (GET_MODE (SET_DEST (set)))
|
&& SET_SRC (set) == CONST0_RTX (GET_MODE (SET_DEST (set)))
|
&& !reg_set_between_p (SET_DEST (set), temp, insn)
|
&& !reg_set_between_p (SET_DEST (set), temp, insn)
|
&& (REGNO_REG_CLASS (REGNO (SET_DEST (set)))
|
&& (REGNO_REG_CLASS (REGNO (SET_DEST (set)))
|
!= REGNO_REG_CLASS (REGNO (operand)))
|
!= REGNO_REG_CLASS (REGNO (operand)))
|
&& REGNO_REG_CLASS (REGNO (SET_DEST (set))) == EXTENDED_REGS
|
&& REGNO_REG_CLASS (REGNO (SET_DEST (set))) == EXTENDED_REGS
|
&& REGNO (SET_DEST (set)) != REGNO (operand)
|
&& REGNO (SET_DEST (set)) != REGNO (operand)
|
&& (!past_call
|
&& (!past_call
|
|| !call_used_regs[REGNO (SET_DEST (set))]))
|
|| !call_used_regs[REGNO (SET_DEST (set))]))
|
{
|
{
|
rtx xoperands[2];
|
rtx xoperands[2];
|
xoperands[0] = operand;
|
xoperands[0] = operand;
|
xoperands[1] = SET_DEST (set);
|
xoperands[1] = SET_DEST (set);
|
|
|
output_asm_insn ("cmp %1,%0", xoperands);
|
output_asm_insn ("cmp %1,%0", xoperands);
|
return "";
|
return "";
|
}
|
}
|
temp = PREV_INSN (temp);
|
temp = PREV_INSN (temp);
|
}
|
}
|
return "cmp 0,%0";
|
return "cmp 0,%0";
|
}
|
}
|
|
|
int
|
int
|
impossible_plus_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED)
|
impossible_plus_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED)
|
{
|
{
|
if (GET_CODE (op) != PLUS)
|
if (GET_CODE (op) != PLUS)
|
return 0;
|
return 0;
|
|
|
if (XEXP (op, 0) == stack_pointer_rtx
|
if (XEXP (op, 0) == stack_pointer_rtx
|
|| XEXP (op, 1) == stack_pointer_rtx)
|
|| XEXP (op, 1) == stack_pointer_rtx)
|
return 1;
|
return 1;
|
|
|
return 0;
|
return 0;
|
}
|
}
|
|
|
/* Similarly, but when using a zero_extract pattern for a btst where
|
/* Similarly, but when using a zero_extract pattern for a btst where
|
the source operand might end up in memory. */
|
the source operand might end up in memory. */
|
int
|
int
|
mask_ok_for_mem_btst (int len, int bit)
|
mask_ok_for_mem_btst (int len, int bit)
|
{
|
{
|
unsigned int mask = 0;
|
unsigned int mask = 0;
|
|
|
while (len > 0)
|
while (len > 0)
|
{
|
{
|
mask |= (1 << bit);
|
mask |= (1 << bit);
|
bit++;
|
bit++;
|
len--;
|
len--;
|
}
|
}
|
|
|
/* MASK must bit into an 8bit value. */
|
/* MASK must bit into an 8bit value. */
|
return (((mask & 0xff) == mask)
|
return (((mask & 0xff) == mask)
|
|| ((mask & 0xff00) == mask)
|
|| ((mask & 0xff00) == mask)
|
|| ((mask & 0xff0000) == mask)
|
|| ((mask & 0xff0000) == mask)
|
|| ((mask & 0xff000000) == mask));
|
|| ((mask & 0xff000000) == mask));
|
}
|
}
|
|
|
/* Return 1 if X contains a symbolic expression. We know these
|
/* Return 1 if X contains a symbolic expression. We know these
|
expressions will have one of a few well defined forms, so
|
expressions will have one of a few well defined forms, so
|
we need only check those forms. */
|
we need only check those forms. */
|
int
|
int
|
symbolic_operand (register rtx op, enum machine_mode mode ATTRIBUTE_UNUSED)
|
symbolic_operand (register rtx op, enum machine_mode mode ATTRIBUTE_UNUSED)
|
{
|
{
|
switch (GET_CODE (op))
|
switch (GET_CODE (op))
|
{
|
{
|
case SYMBOL_REF:
|
case SYMBOL_REF:
|
case LABEL_REF:
|
case LABEL_REF:
|
return 1;
|
return 1;
|
case CONST:
|
case CONST:
|
op = XEXP (op, 0);
|
op = XEXP (op, 0);
|
return ((GET_CODE (XEXP (op, 0)) == SYMBOL_REF
|
return ((GET_CODE (XEXP (op, 0)) == SYMBOL_REF
|
|| GET_CODE (XEXP (op, 0)) == LABEL_REF)
|
|| GET_CODE (XEXP (op, 0)) == LABEL_REF)
|
&& GET_CODE (XEXP (op, 1)) == CONST_INT);
|
&& GET_CODE (XEXP (op, 1)) == CONST_INT);
|
default:
|
default:
|
return 0;
|
return 0;
|
}
|
}
|
}
|
}
|
|
|
/* Try machine dependent ways of modifying an illegitimate address
|
/* Try machine dependent ways of modifying an illegitimate address
|
to be legitimate. If we find one, return the new valid address.
|
to be legitimate. If we find one, return the new valid address.
|
This macro is used in only one place: `memory_address' in explow.c.
|
This macro is used in only one place: `memory_address' in explow.c.
|
|
|
OLDX is the address as it was before break_out_memory_refs was called.
|
OLDX is the address as it was before break_out_memory_refs was called.
|
In some cases it is useful to look at this to decide what needs to be done.
|
In some cases it is useful to look at this to decide what needs to be done.
|
|
|
MODE and WIN are passed so that this macro can use
|
MODE and WIN are passed so that this macro can use
|
GO_IF_LEGITIMATE_ADDRESS.
|
GO_IF_LEGITIMATE_ADDRESS.
|
|
|
Normally it is always safe for this macro to do nothing. It exists to
|
Normally it is always safe for this macro to do nothing. It exists to
|
recognize opportunities to optimize the output.
|
recognize opportunities to optimize the output.
|
|
|
But on a few ports with segmented architectures and indexed addressing
|
But on a few ports with segmented architectures and indexed addressing
|
(mn10300, hppa) it is used to rewrite certain problematical addresses. */
|
(mn10300, hppa) it is used to rewrite certain problematical addresses. */
|
rtx
|
rtx
|
legitimize_address (rtx x, rtx oldx ATTRIBUTE_UNUSED,
|
legitimize_address (rtx x, rtx oldx ATTRIBUTE_UNUSED,
|
enum machine_mode mode ATTRIBUTE_UNUSED)
|
enum machine_mode mode ATTRIBUTE_UNUSED)
|
{
|
{
|
if (flag_pic && ! legitimate_pic_operand_p (x))
|
if (flag_pic && ! legitimate_pic_operand_p (x))
|
x = legitimize_pic_address (oldx, NULL_RTX);
|
x = legitimize_pic_address (oldx, NULL_RTX);
|
|
|
/* Uh-oh. We might have an address for x[n-100000]. This needs
|
/* Uh-oh. We might have an address for x[n-100000]. This needs
|
special handling to avoid creating an indexed memory address
|
special handling to avoid creating an indexed memory address
|
with x-100000 as the base. */
|
with x-100000 as the base. */
|
if (GET_CODE (x) == PLUS
|
if (GET_CODE (x) == PLUS
|
&& symbolic_operand (XEXP (x, 1), VOIDmode))
|
&& symbolic_operand (XEXP (x, 1), VOIDmode))
|
{
|
{
|
/* Ugly. We modify things here so that the address offset specified
|
/* Ugly. We modify things here so that the address offset specified
|
by the index expression is computed first, then added to x to form
|
by the index expression is computed first, then added to x to form
|
the entire address. */
|
the entire address. */
|
|
|
rtx regx1, regy1, regy2, y;
|
rtx regx1, regy1, regy2, y;
|
|
|
/* Strip off any CONST. */
|
/* Strip off any CONST. */
|
y = XEXP (x, 1);
|
y = XEXP (x, 1);
|
if (GET_CODE (y) == CONST)
|
if (GET_CODE (y) == CONST)
|
y = XEXP (y, 0);
|
y = XEXP (y, 0);
|
|
|
if (GET_CODE (y) == PLUS || GET_CODE (y) == MINUS)
|
if (GET_CODE (y) == PLUS || GET_CODE (y) == MINUS)
|
{
|
{
|
regx1 = force_reg (Pmode, force_operand (XEXP (x, 0), 0));
|
regx1 = force_reg (Pmode, force_operand (XEXP (x, 0), 0));
|
regy1 = force_reg (Pmode, force_operand (XEXP (y, 0), 0));
|
regy1 = force_reg (Pmode, force_operand (XEXP (y, 0), 0));
|
regy2 = force_reg (Pmode, force_operand (XEXP (y, 1), 0));
|
regy2 = force_reg (Pmode, force_operand (XEXP (y, 1), 0));
|
regx1 = force_reg (Pmode,
|
regx1 = force_reg (Pmode,
|
gen_rtx_fmt_ee (GET_CODE (y), Pmode, regx1, regy2));
|
gen_rtx_fmt_ee (GET_CODE (y), Pmode, regx1, regy2));
|
return force_reg (Pmode, gen_rtx_PLUS (Pmode, regx1, regy1));
|
return force_reg (Pmode, gen_rtx_PLUS (Pmode, regx1, regy1));
|
}
|
}
|
}
|
}
|
return x;
|
return x;
|
}
|
}
|
|
|
/* Convert a non-PIC address in `orig' to a PIC address using @GOT or
|
/* Convert a non-PIC address in `orig' to a PIC address using @GOT or
|
@GOTOFF in `reg'. */
|
@GOTOFF in `reg'. */
|
rtx
|
rtx
|
legitimize_pic_address (rtx orig, rtx reg)
|
legitimize_pic_address (rtx orig, rtx reg)
|
{
|
{
|
if (GET_CODE (orig) == LABEL_REF
|
if (GET_CODE (orig) == LABEL_REF
|
|| (GET_CODE (orig) == SYMBOL_REF
|
|| (GET_CODE (orig) == SYMBOL_REF
|
&& (CONSTANT_POOL_ADDRESS_P (orig)
|
&& (CONSTANT_POOL_ADDRESS_P (orig)
|
|| ! MN10300_GLOBAL_P (orig))))
|
|| ! MN10300_GLOBAL_P (orig))))
|
{
|
{
|
if (reg == 0)
|
if (reg == 0)
|
reg = gen_reg_rtx (Pmode);
|
reg = gen_reg_rtx (Pmode);
|
|
|
emit_insn (gen_symGOTOFF2reg (reg, orig));
|
emit_insn (gen_symGOTOFF2reg (reg, orig));
|
return reg;
|
return reg;
|
}
|
}
|
else if (GET_CODE (orig) == SYMBOL_REF)
|
else if (GET_CODE (orig) == SYMBOL_REF)
|
{
|
{
|
if (reg == 0)
|
if (reg == 0)
|
reg = gen_reg_rtx (Pmode);
|
reg = gen_reg_rtx (Pmode);
|
|
|
emit_insn (gen_symGOT2reg (reg, orig));
|
emit_insn (gen_symGOT2reg (reg, orig));
|
return reg;
|
return reg;
|
}
|
}
|
return orig;
|
return orig;
|
}
|
}
|
|
|
/* Return zero if X references a SYMBOL_REF or LABEL_REF whose symbol
|
/* Return zero if X references a SYMBOL_REF or LABEL_REF whose symbol
|
isn't protected by a PIC unspec; nonzero otherwise. */
|
isn't protected by a PIC unspec; nonzero otherwise. */
|
int
|
int
|
legitimate_pic_operand_p (rtx x)
|
legitimate_pic_operand_p (rtx x)
|
{
|
{
|
register const char *fmt;
|
register const char *fmt;
|
register int i;
|
register int i;
|
|
|
if (GET_CODE (x) == SYMBOL_REF || GET_CODE (x) == LABEL_REF)
|
if (GET_CODE (x) == SYMBOL_REF || GET_CODE (x) == LABEL_REF)
|
return 0;
|
return 0;
|
|
|
if (GET_CODE (x) == UNSPEC
|
if (GET_CODE (x) == UNSPEC
|
&& (XINT (x, 1) == UNSPEC_PIC
|
&& (XINT (x, 1) == UNSPEC_PIC
|
|| XINT (x, 1) == UNSPEC_GOT
|
|| XINT (x, 1) == UNSPEC_GOT
|
|| XINT (x, 1) == UNSPEC_GOTOFF
|
|| XINT (x, 1) == UNSPEC_GOTOFF
|
|| XINT (x, 1) == UNSPEC_PLT))
|
|| XINT (x, 1) == UNSPEC_PLT))
|
return 1;
|
return 1;
|
|
|
fmt = GET_RTX_FORMAT (GET_CODE (x));
|
fmt = GET_RTX_FORMAT (GET_CODE (x));
|
for (i = GET_RTX_LENGTH (GET_CODE (x)) - 1; i >= 0; i--)
|
for (i = GET_RTX_LENGTH (GET_CODE (x)) - 1; i >= 0; i--)
|
{
|
{
|
if (fmt[i] == 'E')
|
if (fmt[i] == 'E')
|
{
|
{
|
register int j;
|
register int j;
|
|
|
for (j = XVECLEN (x, i) - 1; j >= 0; j--)
|
for (j = XVECLEN (x, i) - 1; j >= 0; j--)
|
if (! legitimate_pic_operand_p (XVECEXP (x, i, j)))
|
if (! legitimate_pic_operand_p (XVECEXP (x, i, j)))
|
return 0;
|
return 0;
|
}
|
}
|
else if (fmt[i] == 'e' && ! legitimate_pic_operand_p (XEXP (x, i)))
|
else if (fmt[i] == 'e' && ! legitimate_pic_operand_p (XEXP (x, i)))
|
return 0;
|
return 0;
|
}
|
}
|
|
|
return 1;
|
return 1;
|
}
|
}
|
|
|
/* Return TRUE if the address X, taken from a (MEM:MODE X) rtx, is
|
/* Return TRUE if the address X, taken from a (MEM:MODE X) rtx, is
|
legitimate, and FALSE otherwise. */
|
legitimate, and FALSE otherwise. */
|
bool
|
bool
|
legitimate_address_p (enum machine_mode mode, rtx x, int strict)
|
legitimate_address_p (enum machine_mode mode, rtx x, int strict)
|
{
|
{
|
if (CONSTANT_ADDRESS_P (x)
|
if (CONSTANT_ADDRESS_P (x)
|
&& (! flag_pic || legitimate_pic_operand_p (x)))
|
&& (! flag_pic || legitimate_pic_operand_p (x)))
|
return TRUE;
|
return TRUE;
|
|
|
if (RTX_OK_FOR_BASE_P (x, strict))
|
if (RTX_OK_FOR_BASE_P (x, strict))
|
return TRUE;
|
return TRUE;
|
|
|
if (TARGET_AM33
|
if (TARGET_AM33
|
&& GET_CODE (x) == POST_INC
|
&& GET_CODE (x) == POST_INC
|
&& RTX_OK_FOR_BASE_P (XEXP (x, 0), strict)
|
&& RTX_OK_FOR_BASE_P (XEXP (x, 0), strict)
|
&& (mode == SImode || mode == SFmode || mode == HImode))
|
&& (mode == SImode || mode == SFmode || mode == HImode))
|
return TRUE;
|
return TRUE;
|
|
|
if (GET_CODE (x) == PLUS)
|
if (GET_CODE (x) == PLUS)
|
{
|
{
|
rtx base = 0, index = 0;
|
rtx base = 0, index = 0;
|
|
|
if (REG_P (XEXP (x, 0))
|
if (REG_P (XEXP (x, 0))
|
&& REGNO_STRICT_OK_FOR_BASE_P (REGNO (XEXP (x, 0)), strict))
|
&& REGNO_STRICT_OK_FOR_BASE_P (REGNO (XEXP (x, 0)), strict))
|
{
|
{
|
base = XEXP (x, 0);
|
base = XEXP (x, 0);
|
index = XEXP (x, 1);
|
index = XEXP (x, 1);
|
}
|
}
|
|
|
if (REG_P (XEXP (x, 1))
|
if (REG_P (XEXP (x, 1))
|
&& REGNO_STRICT_OK_FOR_BASE_P (REGNO (XEXP (x, 1)), strict))
|
&& REGNO_STRICT_OK_FOR_BASE_P (REGNO (XEXP (x, 1)), strict))
|
{
|
{
|
base = XEXP (x, 1);
|
base = XEXP (x, 1);
|
index = XEXP (x, 0);
|
index = XEXP (x, 0);
|
}
|
}
|
|
|
if (base != 0 && index != 0)
|
if (base != 0 && index != 0)
|
{
|
{
|
if (GET_CODE (index) == CONST_INT)
|
if (GET_CODE (index) == CONST_INT)
|
return TRUE;
|
return TRUE;
|
if (GET_CODE (index) == CONST
|
if (GET_CODE (index) == CONST
|
&& GET_CODE (XEXP (index, 0)) != PLUS
|
&& GET_CODE (XEXP (index, 0)) != PLUS
|
&& (! flag_pic
|
&& (! flag_pic
|
|| legitimate_pic_operand_p (index)))
|
|| legitimate_pic_operand_p (index)))
|
return TRUE;
|
return TRUE;
|
}
|
}
|
}
|
}
|
|
|
return FALSE;
|
return FALSE;
|
}
|
}
|
|
|
static int
|
static int
|
mn10300_address_cost_1 (rtx x, int *unsig)
|
mn10300_address_cost_1 (rtx x, int *unsig)
|
{
|
{
|
switch (GET_CODE (x))
|
switch (GET_CODE (x))
|
{
|
{
|
case REG:
|
case REG:
|
switch (REGNO_REG_CLASS (REGNO (x)))
|
switch (REGNO_REG_CLASS (REGNO (x)))
|
{
|
{
|
case SP_REGS:
|
case SP_REGS:
|
*unsig = 1;
|
*unsig = 1;
|
return 0;
|
return 0;
|
|
|
case ADDRESS_REGS:
|
case ADDRESS_REGS:
|
return 1;
|
return 1;
|
|
|
case DATA_REGS:
|
case DATA_REGS:
|
case EXTENDED_REGS:
|
case EXTENDED_REGS:
|
case FP_REGS:
|
case FP_REGS:
|
return 3;
|
return 3;
|
|
|
case NO_REGS:
|
case NO_REGS:
|
return 5;
|
return 5;
|
|
|
default:
|
default:
|
gcc_unreachable ();
|
gcc_unreachable ();
|
}
|
}
|
|
|
case PLUS:
|
case PLUS:
|
case MINUS:
|
case MINUS:
|
case ASHIFT:
|
case ASHIFT:
|
case AND:
|
case AND:
|
case IOR:
|
case IOR:
|
return (mn10300_address_cost_1 (XEXP (x, 0), unsig)
|
return (mn10300_address_cost_1 (XEXP (x, 0), unsig)
|
+ mn10300_address_cost_1 (XEXP (x, 1), unsig));
|
+ mn10300_address_cost_1 (XEXP (x, 1), unsig));
|
|
|
case EXPR_LIST:
|
case EXPR_LIST:
|
case SUBREG:
|
case SUBREG:
|
case MEM:
|
case MEM:
|
return mn10300_address_cost (XEXP (x, 0));
|
return mn10300_address_cost (XEXP (x, 0));
|
|
|
case ZERO_EXTEND:
|
case ZERO_EXTEND:
|
*unsig = 1;
|
*unsig = 1;
|
return mn10300_address_cost_1 (XEXP (x, 0), unsig);
|
return mn10300_address_cost_1 (XEXP (x, 0), unsig);
|
|
|
case CONST_INT:
|
case CONST_INT:
|
if (INTVAL (x) == 0)
|
if (INTVAL (x) == 0)
|
return 0;
|
return 0;
|
if (INTVAL (x) + (*unsig ? 0 : 0x80) < 0x100)
|
if (INTVAL (x) + (*unsig ? 0 : 0x80) < 0x100)
|
return 1;
|
return 1;
|
if (INTVAL (x) + (*unsig ? 0 : 0x8000) < 0x10000)
|
if (INTVAL (x) + (*unsig ? 0 : 0x8000) < 0x10000)
|
return 3;
|
return 3;
|
if (INTVAL (x) + (*unsig ? 0 : 0x800000) < 0x1000000)
|
if (INTVAL (x) + (*unsig ? 0 : 0x800000) < 0x1000000)
|
return 5;
|
return 5;
|
return 7;
|
return 7;
|
|
|
case CONST:
|
case CONST:
|
case SYMBOL_REF:
|
case SYMBOL_REF:
|
case LABEL_REF:
|
case LABEL_REF:
|
return 8;
|
return 8;
|
|
|
default:
|
default:
|
gcc_unreachable ();
|
gcc_unreachable ();
|
|
|
}
|
}
|
}
|
}
|
|
|
static int
|
static int
|
mn10300_address_cost (rtx x)
|
mn10300_address_cost (rtx x)
|
{
|
{
|
int s = 0;
|
int s = 0;
|
return mn10300_address_cost_1 (x, &s);
|
return mn10300_address_cost_1 (x, &s);
|
}
|
}
|
|
|
static bool
|
static bool
|
mn10300_rtx_costs (rtx x, int code, int outer_code, int *total)
|
mn10300_rtx_costs (rtx x, int code, int outer_code, int *total)
|
{
|
{
|
switch (code)
|
switch (code)
|
{
|
{
|
case CONST_INT:
|
case CONST_INT:
|
/* Zeros are extremely cheap. */
|
/* Zeros are extremely cheap. */
|
if (INTVAL (x) == 0 && outer_code == SET)
|
if (INTVAL (x) == 0 && outer_code == SET)
|
*total = 0;
|
*total = 0;
|
/* If it fits in 8 bits, then it's still relatively cheap. */
|
/* If it fits in 8 bits, then it's still relatively cheap. */
|
else if (INT_8_BITS (INTVAL (x)))
|
else if (INT_8_BITS (INTVAL (x)))
|
*total = 1;
|
*total = 1;
|
/* This is the "base" cost, includes constants where either the
|
/* This is the "base" cost, includes constants where either the
|
upper or lower 16bits are all zeros. */
|
upper or lower 16bits are all zeros. */
|
else if (INT_16_BITS (INTVAL (x))
|
else if (INT_16_BITS (INTVAL (x))
|
|| (INTVAL (x) & 0xffff) == 0
|
|| (INTVAL (x) & 0xffff) == 0
|
|| (INTVAL (x) & 0xffff0000) == 0)
|
|| (INTVAL (x) & 0xffff0000) == 0)
|
*total = 2;
|
*total = 2;
|
else
|
else
|
*total = 4;
|
*total = 4;
|
return true;
|
return true;
|
|
|
case CONST:
|
case CONST:
|
case LABEL_REF:
|
case LABEL_REF:
|
case SYMBOL_REF:
|
case SYMBOL_REF:
|
/* These are more costly than a CONST_INT, but we can relax them,
|
/* These are more costly than a CONST_INT, but we can relax them,
|
so they're less costly than a CONST_DOUBLE. */
|
so they're less costly than a CONST_DOUBLE. */
|
*total = 6;
|
*total = 6;
|
return true;
|
return true;
|
|
|
case CONST_DOUBLE:
|
case CONST_DOUBLE:
|
/* We don't optimize CONST_DOUBLEs well nor do we relax them well,
|
/* We don't optimize CONST_DOUBLEs well nor do we relax them well,
|
so their cost is very high. */
|
so their cost is very high. */
|
*total = 8;
|
*total = 8;
|
return true;
|
return true;
|
|
|
/* ??? This probably needs more work. */
|
/* ??? This probably needs more work. */
|
case MOD:
|
case MOD:
|
case DIV:
|
case DIV:
|
case MULT:
|
case MULT:
|
*total = 8;
|
*total = 8;
|
return true;
|
return true;
|
|
|
default:
|
default:
|
return false;
|
return false;
|
}
|
}
|
}
|
}
|
|
|
/* Check whether a constant used to initialize a DImode or DFmode can
|
/* Check whether a constant used to initialize a DImode or DFmode can
|
use a clr instruction. The code here must be kept in sync with
|
use a clr instruction. The code here must be kept in sync with
|
movdf and movdi. */
|
movdf and movdi. */
|
|
|
bool
|
bool
|
mn10300_wide_const_load_uses_clr (rtx operands[2])
|
mn10300_wide_const_load_uses_clr (rtx operands[2])
|
{
|
{
|
long val[2];
|
long val[2];
|
|
|
if (GET_CODE (operands[0]) != REG
|
if (GET_CODE (operands[0]) != REG
|
|| REGNO_REG_CLASS (REGNO (operands[0])) != DATA_REGS)
|
|| REGNO_REG_CLASS (REGNO (operands[0])) != DATA_REGS)
|
return false;
|
return false;
|
|
|
switch (GET_CODE (operands[1]))
|
switch (GET_CODE (operands[1]))
|
{
|
{
|
case CONST_INT:
|
case CONST_INT:
|
{
|
{
|
rtx low, high;
|
rtx low, high;
|
split_double (operands[1], &low, &high);
|
split_double (operands[1], &low, &high);
|
val[0] = INTVAL (low);
|
val[0] = INTVAL (low);
|
val[1] = INTVAL (high);
|
val[1] = INTVAL (high);
|
}
|
}
|
break;
|
break;
|
|
|
case CONST_DOUBLE:
|
case CONST_DOUBLE:
|
if (GET_MODE (operands[1]) == DFmode)
|
if (GET_MODE (operands[1]) == DFmode)
|
{
|
{
|
REAL_VALUE_TYPE rv;
|
REAL_VALUE_TYPE rv;
|
|
|
REAL_VALUE_FROM_CONST_DOUBLE (rv, operands[1]);
|
REAL_VALUE_FROM_CONST_DOUBLE (rv, operands[1]);
|
REAL_VALUE_TO_TARGET_DOUBLE (rv, val);
|
REAL_VALUE_TO_TARGET_DOUBLE (rv, val);
|
}
|
}
|
else if (GET_MODE (operands[1]) == VOIDmode
|
else if (GET_MODE (operands[1]) == VOIDmode
|
|| GET_MODE (operands[1]) == DImode)
|
|| GET_MODE (operands[1]) == DImode)
|
{
|
{
|
val[0] = CONST_DOUBLE_LOW (operands[1]);
|
val[0] = CONST_DOUBLE_LOW (operands[1]);
|
val[1] = CONST_DOUBLE_HIGH (operands[1]);
|
val[1] = CONST_DOUBLE_HIGH (operands[1]);
|
}
|
}
|
break;
|
break;
|
|
|
default:
|
default:
|
return false;
|
return false;
|
}
|
}
|
|
|
return val[0] == 0 || val[1] == 0;
|
return val[0] == 0 || val[1] == 0;
|
}
|
}
|
/* If using PIC, mark a SYMBOL_REF for a non-global symbol so that we
|
/* If using PIC, mark a SYMBOL_REF for a non-global symbol so that we
|
may access it using GOTOFF instead of GOT. */
|
may access it using GOTOFF instead of GOT. */
|
|
|
static void
|
static void
|
mn10300_encode_section_info (tree decl, rtx rtl, int first ATTRIBUTE_UNUSED)
|
mn10300_encode_section_info (tree decl, rtx rtl, int first ATTRIBUTE_UNUSED)
|
{
|
{
|
rtx symbol;
|
rtx symbol;
|
|
|
if (GET_CODE (rtl) != MEM)
|
if (GET_CODE (rtl) != MEM)
|
return;
|
return;
|
symbol = XEXP (rtl, 0);
|
symbol = XEXP (rtl, 0);
|
if (GET_CODE (symbol) != SYMBOL_REF)
|
if (GET_CODE (symbol) != SYMBOL_REF)
|
return;
|
return;
|
|
|
if (flag_pic)
|
if (flag_pic)
|
SYMBOL_REF_FLAG (symbol) = (*targetm.binds_local_p) (decl);
|
SYMBOL_REF_FLAG (symbol) = (*targetm.binds_local_p) (decl);
|
}
|
}
|
|
|