blob: b5a61a36450001d072f2d2c22d8a5e9bad0e2141 [file] [log] [blame]
/* Subroutines for insn-output.c for ATMEL AVR micro controllers
Copyright (C) 1998, 1999, 2000, 2001, 2002, 2004, 2005, 2006, 2007, 2008,
2009, 2010 Free Software Foundation, Inc.
Contributed by Denis Chertykov (chertykov@gmail.com)
This file is part of GCC.
GCC is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GCC is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "tm.h"
#include "rtl.h"
#include "regs.h"
#include "hard-reg-set.h"
#include "insn-config.h"
#include "conditions.h"
#include "insn-attr.h"
#include "flags.h"
#include "reload.h"
#include "tree.h"
#include "output.h"
#include "expr.h"
#include "diagnostic-core.h"
#include "obstack.h"
#include "function.h"
#include "recog.h"
#include "ggc.h"
#include "tm_p.h"
#include "target.h"
#include "target-def.h"
#include "params.h"
#include "df.h"
/* Maximal allowed offset for an address in the LD command */
#define MAX_LD_OFFSET(MODE) (64 - (signed)GET_MODE_SIZE (MODE))
static void avr_option_override (void);
static int avr_naked_function_p (tree);
static int interrupt_function_p (tree);
static int signal_function_p (tree);
static int avr_OS_task_function_p (tree);
static int avr_OS_main_function_p (tree);
static int avr_regs_to_save (HARD_REG_SET *);
static int get_sequence_length (rtx insns);
static int sequent_regs_live (void);
static const char *ptrreg_to_str (int);
static const char *cond_string (enum rtx_code);
static int avr_num_arg_regs (enum machine_mode, const_tree);
static RTX_CODE compare_condition (rtx insn);
static rtx avr_legitimize_address (rtx, rtx, enum machine_mode);
static int compare_sign_p (rtx insn);
static tree avr_handle_progmem_attribute (tree *, tree, tree, int, bool *);
static tree avr_handle_fndecl_attribute (tree *, tree, tree, int, bool *);
static tree avr_handle_fntype_attribute (tree *, tree, tree, int, bool *);
static bool avr_assemble_integer (rtx, unsigned int, int);
static void avr_file_start (void);
static void avr_file_end (void);
static bool avr_legitimate_address_p (enum machine_mode, rtx, bool);
static void avr_asm_function_end_prologue (FILE *);
static void avr_asm_function_begin_epilogue (FILE *);
static bool avr_cannot_modify_jumps_p (void);
static rtx avr_function_value (const_tree, const_tree, bool);
static void avr_insert_attributes (tree, tree *);
static void avr_asm_init_sections (void);
static unsigned int avr_section_type_flags (tree, const char *, int);
static void avr_encode_section_info (tree, rtx, int);
static void avr_reorg (void);
static void avr_asm_out_ctor (rtx, int);
static void avr_asm_out_dtor (rtx, int);
static int avr_register_move_cost (enum machine_mode, reg_class_t, reg_class_t);
static int avr_memory_move_cost (enum machine_mode, reg_class_t, bool);
static int avr_operand_rtx_cost (rtx, enum machine_mode, enum rtx_code, bool);
static bool avr_rtx_costs (rtx, int, int, int *, bool);
static int avr_address_cost (rtx, bool);
static bool avr_return_in_memory (const_tree, const_tree);
static struct machine_function * avr_init_machine_status (void);
static rtx avr_builtin_setjmp_frame_value (void);
static bool avr_hard_regno_scratch_ok (unsigned int);
static unsigned int avr_case_values_threshold (void);
static bool avr_frame_pointer_required_p (void);
static bool avr_can_eliminate (const int, const int);
static bool avr_class_likely_spilled_p (reg_class_t c);
static rtx avr_function_arg (CUMULATIVE_ARGS *, enum machine_mode,
const_tree, bool);
static void avr_function_arg_advance (CUMULATIVE_ARGS *, enum machine_mode,
const_tree, bool);
static void avr_help (void);
/* Allocate registers from r25 to r8 for parameters for function calls. */
#define FIRST_CUM_REG 26
/* Temporary register RTX (gen_rtx_REG (QImode, TMP_REGNO)) */
static GTY(()) rtx tmp_reg_rtx;
/* Zeroed register RTX (gen_rtx_REG (QImode, ZERO_REGNO)) */
static GTY(()) rtx zero_reg_rtx;
/* AVR register names {"r0", "r1", ..., "r31"} */
static const char *const avr_regnames[] = REGISTER_NAMES;
/* Preprocessor macros to define depending on MCU type. */
const char *avr_extra_arch_macro;
/* Current architecture. */
const struct base_arch_s *avr_current_arch;
/* Current device. */
const struct mcu_type_s *avr_current_device;
section *progmem_section;
/* AVR attributes. */
static const struct attribute_spec avr_attribute_table[] =
{
/* { name, min_len, max_len, decl_req, type_req, fn_type_req, handler } */
{ "progmem", 0, 0, false, false, false, avr_handle_progmem_attribute },
{ "signal", 0, 0, true, false, false, avr_handle_fndecl_attribute },
{ "interrupt", 0, 0, true, false, false, avr_handle_fndecl_attribute },
{ "naked", 0, 0, false, true, true, avr_handle_fntype_attribute },
{ "OS_task", 0, 0, false, true, true, avr_handle_fntype_attribute },
{ "OS_main", 0, 0, false, true, true, avr_handle_fntype_attribute },
{ NULL, 0, 0, false, false, false, NULL }
};
/* Implement TARGET_OPTION_OPTIMIZATION_TABLE. */
static const struct default_options avr_option_optimization_table[] =
{
{ OPT_LEVELS_1_PLUS, OPT_fomit_frame_pointer, NULL, 1 },
{ OPT_LEVELS_NONE, 0, NULL, 0 }
};
/* Initialize the GCC target structure. */
#undef TARGET_ASM_ALIGNED_HI_OP
#define TARGET_ASM_ALIGNED_HI_OP "\t.word\t"
#undef TARGET_ASM_ALIGNED_SI_OP
#define TARGET_ASM_ALIGNED_SI_OP "\t.long\t"
#undef TARGET_ASM_UNALIGNED_HI_OP
#define TARGET_ASM_UNALIGNED_HI_OP "\t.word\t"
#undef TARGET_ASM_UNALIGNED_SI_OP
#define TARGET_ASM_UNALIGNED_SI_OP "\t.long\t"
#undef TARGET_ASM_INTEGER
#define TARGET_ASM_INTEGER avr_assemble_integer
#undef TARGET_ASM_FILE_START
#define TARGET_ASM_FILE_START avr_file_start
#undef TARGET_ASM_FILE_START_FILE_DIRECTIVE
#define TARGET_ASM_FILE_START_FILE_DIRECTIVE true
#undef TARGET_ASM_FILE_END
#define TARGET_ASM_FILE_END avr_file_end
#undef TARGET_ASM_FUNCTION_END_PROLOGUE
#define TARGET_ASM_FUNCTION_END_PROLOGUE avr_asm_function_end_prologue
#undef TARGET_ASM_FUNCTION_BEGIN_EPILOGUE
#define TARGET_ASM_FUNCTION_BEGIN_EPILOGUE avr_asm_function_begin_epilogue
#undef TARGET_FUNCTION_VALUE
#define TARGET_FUNCTION_VALUE avr_function_value
#undef TARGET_ATTRIBUTE_TABLE
#define TARGET_ATTRIBUTE_TABLE avr_attribute_table
#undef TARGET_ASM_FUNCTION_RODATA_SECTION
#define TARGET_ASM_FUNCTION_RODATA_SECTION default_no_function_rodata_section
#undef TARGET_INSERT_ATTRIBUTES
#define TARGET_INSERT_ATTRIBUTES avr_insert_attributes
#undef TARGET_SECTION_TYPE_FLAGS
#define TARGET_SECTION_TYPE_FLAGS avr_section_type_flags
#undef TARGET_ENCODE_SECTION_INFO
#define TARGET_ENCODE_SECTION_INFO avr_encode_section_info
#undef TARGET_REGISTER_MOVE_COST
#define TARGET_REGISTER_MOVE_COST avr_register_move_cost
#undef TARGET_MEMORY_MOVE_COST
#define TARGET_MEMORY_MOVE_COST avr_memory_move_cost
#undef TARGET_RTX_COSTS
#define TARGET_RTX_COSTS avr_rtx_costs
#undef TARGET_ADDRESS_COST
#define TARGET_ADDRESS_COST avr_address_cost
#undef TARGET_MACHINE_DEPENDENT_REORG
#define TARGET_MACHINE_DEPENDENT_REORG avr_reorg
#undef TARGET_FUNCTION_ARG
#define TARGET_FUNCTION_ARG avr_function_arg
#undef TARGET_FUNCTION_ARG_ADVANCE
#define TARGET_FUNCTION_ARG_ADVANCE avr_function_arg_advance
#undef TARGET_LEGITIMIZE_ADDRESS
#define TARGET_LEGITIMIZE_ADDRESS avr_legitimize_address
#undef TARGET_RETURN_IN_MEMORY
#define TARGET_RETURN_IN_MEMORY avr_return_in_memory
#undef TARGET_STRICT_ARGUMENT_NAMING
#define TARGET_STRICT_ARGUMENT_NAMING hook_bool_CUMULATIVE_ARGS_true
#undef TARGET_BUILTIN_SETJMP_FRAME_VALUE
#define TARGET_BUILTIN_SETJMP_FRAME_VALUE avr_builtin_setjmp_frame_value
#undef TARGET_HARD_REGNO_SCRATCH_OK
#define TARGET_HARD_REGNO_SCRATCH_OK avr_hard_regno_scratch_ok
#undef TARGET_CASE_VALUES_THRESHOLD
#define TARGET_CASE_VALUES_THRESHOLD avr_case_values_threshold
#undef TARGET_LEGITIMATE_ADDRESS_P
#define TARGET_LEGITIMATE_ADDRESS_P avr_legitimate_address_p
#undef TARGET_FRAME_POINTER_REQUIRED
#define TARGET_FRAME_POINTER_REQUIRED avr_frame_pointer_required_p
#undef TARGET_CAN_ELIMINATE
#define TARGET_CAN_ELIMINATE avr_can_eliminate
#undef TARGET_CLASS_LIKELY_SPILLED_P
#define TARGET_CLASS_LIKELY_SPILLED_P avr_class_likely_spilled_p
#undef TARGET_OPTION_OVERRIDE
#define TARGET_OPTION_OVERRIDE avr_option_override
#undef TARGET_OPTION_OPTIMIZATION_TABLE
#define TARGET_OPTION_OPTIMIZATION_TABLE avr_option_optimization_table
#undef TARGET_CANNOT_MODIFY_JUMPS_P
#define TARGET_CANNOT_MODIFY_JUMPS_P avr_cannot_modify_jumps_p
#undef TARGET_HELP
#define TARGET_HELP avr_help
#undef TARGET_EXCEPT_UNWIND_INFO
#define TARGET_EXCEPT_UNWIND_INFO sjlj_except_unwind_info
struct gcc_target targetm = TARGET_INITIALIZER;
static void
avr_option_override (void)
{
const struct mcu_type_s *t;
flag_delete_null_pointer_checks = 0;
for (t = avr_mcu_types; t->name; t++)
if (strcmp (t->name, avr_mcu_name) == 0)
break;
if (!t->name)
{
error ("unrecognized argument to -mmcu= option: %qs", avr_mcu_name);
inform (input_location, "See --target-help for supported MCUs");
}
avr_current_device = t;
avr_current_arch = &avr_arch_types[avr_current_device->arch];
avr_extra_arch_macro = avr_current_device->macro;
tmp_reg_rtx = gen_rtx_REG (QImode, TMP_REGNO);
zero_reg_rtx = gen_rtx_REG (QImode, ZERO_REGNO);
init_machine_status = avr_init_machine_status;
}
/* Implement TARGET_HELP */
/* Report extra information for --target-help */
static void
avr_help (void)
{
const struct mcu_type_s *t;
const char * const indent = " ";
int len;
/* Give a list of MCUs that are accepted by -mmcu=* .
Note that MCUs supported by the compiler might differ from
MCUs supported by binutils. */
len = strlen (indent);
printf ("Known MCU names:\n%s", indent);
/* Print a blank-separated list of all supported MCUs */
for (t = avr_mcu_types; t->name; t++)
{
printf ("%s ", t->name);
len += 1 + strlen (t->name);
/* Break long lines */
if (len > 66 && (t+1)->name)
{
printf ("\n%s", indent);
len = strlen (indent);
}
}
printf ("\n\n");
}
/* return register class from register number. */
static const enum reg_class reg_class_tab[]={
GENERAL_REGS,GENERAL_REGS,GENERAL_REGS,GENERAL_REGS,GENERAL_REGS,
GENERAL_REGS,GENERAL_REGS,GENERAL_REGS,GENERAL_REGS,GENERAL_REGS,
GENERAL_REGS,GENERAL_REGS,GENERAL_REGS,GENERAL_REGS,GENERAL_REGS,
GENERAL_REGS, /* r0 - r15 */
LD_REGS,LD_REGS,LD_REGS,LD_REGS,LD_REGS,LD_REGS,LD_REGS,
LD_REGS, /* r16 - 23 */
ADDW_REGS,ADDW_REGS, /* r24,r25 */
POINTER_X_REGS,POINTER_X_REGS, /* r26,27 */
POINTER_Y_REGS,POINTER_Y_REGS, /* r28,r29 */
POINTER_Z_REGS,POINTER_Z_REGS, /* r30,r31 */
STACK_REG,STACK_REG /* SPL,SPH */
};
/* Function to set up the backend function structure. */
static struct machine_function *
avr_init_machine_status (void)
{
return ggc_alloc_cleared_machine_function ();
}
/* Return register class for register R. */
enum reg_class
avr_regno_reg_class (int r)
{
if (r <= 33)
return reg_class_tab[r];
return ALL_REGS;
}
/* Return nonzero if FUNC is a naked function. */
static int
avr_naked_function_p (tree func)
{
tree a;
gcc_assert (TREE_CODE (func) == FUNCTION_DECL);
a = lookup_attribute ("naked", TYPE_ATTRIBUTES (TREE_TYPE (func)));
return a != NULL_TREE;
}
/* Return nonzero if FUNC is an interrupt function as specified
by the "interrupt" attribute. */
static int
interrupt_function_p (tree func)
{
tree a;
if (TREE_CODE (func) != FUNCTION_DECL)
return 0;
a = lookup_attribute ("interrupt", DECL_ATTRIBUTES (func));
return a != NULL_TREE;
}
/* Return nonzero if FUNC is a signal function as specified
by the "signal" attribute. */
static int
signal_function_p (tree func)
{
tree a;
if (TREE_CODE (func) != FUNCTION_DECL)
return 0;
a = lookup_attribute ("signal", DECL_ATTRIBUTES (func));
return a != NULL_TREE;
}
/* Return nonzero if FUNC is a OS_task function. */
static int
avr_OS_task_function_p (tree func)
{
tree a;
gcc_assert (TREE_CODE (func) == FUNCTION_DECL);
a = lookup_attribute ("OS_task", TYPE_ATTRIBUTES (TREE_TYPE (func)));
return a != NULL_TREE;
}
/* Return nonzero if FUNC is a OS_main function. */
static int
avr_OS_main_function_p (tree func)
{
tree a;
gcc_assert (TREE_CODE (func) == FUNCTION_DECL);
a = lookup_attribute ("OS_main", TYPE_ATTRIBUTES (TREE_TYPE (func)));
return a != NULL_TREE;
}
/* Return the number of hard registers to push/pop in the prologue/epilogue
of the current function, and optionally store these registers in SET. */
static int
avr_regs_to_save (HARD_REG_SET *set)
{
int reg, count;
int int_or_sig_p = (interrupt_function_p (current_function_decl)
|| signal_function_p (current_function_decl));
if (set)
CLEAR_HARD_REG_SET (*set);
count = 0;
/* No need to save any registers if the function never returns or
is have "OS_task" or "OS_main" attribute. */
if (TREE_THIS_VOLATILE (current_function_decl)
|| cfun->machine->is_OS_task
|| cfun->machine->is_OS_main)
return 0;
for (reg = 0; reg < 32; reg++)
{
/* Do not push/pop __tmp_reg__, __zero_reg__, as well as
any global register variables. */
if (fixed_regs[reg])
continue;
if ((int_or_sig_p && !current_function_is_leaf && call_used_regs[reg])
|| (df_regs_ever_live_p (reg)
&& (int_or_sig_p || !call_used_regs[reg])
&& !(frame_pointer_needed
&& (reg == REG_Y || reg == (REG_Y+1)))))
{
if (set)
SET_HARD_REG_BIT (*set, reg);
count++;
}
}
return count;
}
/* Return true if register FROM can be eliminated via register TO. */
bool
avr_can_eliminate (const int from, const int to)
{
return ((from == ARG_POINTER_REGNUM && to == FRAME_POINTER_REGNUM)
|| ((from == FRAME_POINTER_REGNUM
|| from == FRAME_POINTER_REGNUM + 1)
&& !frame_pointer_needed));
}
/* Compute offset between arg_pointer and frame_pointer. */
int
avr_initial_elimination_offset (int from, int to)
{
if (from == FRAME_POINTER_REGNUM && to == STACK_POINTER_REGNUM)
return 0;
else
{
int offset = frame_pointer_needed ? 2 : 0;
int avr_pc_size = AVR_HAVE_EIJMP_EICALL ? 3 : 2;
offset += avr_regs_to_save (NULL);
return get_frame_size () + (avr_pc_size) + 1 + offset;
}
}
/* Actual start of frame is virtual_stack_vars_rtx this is offset from
frame pointer by +STARTING_FRAME_OFFSET.
Using saved frame = virtual_stack_vars_rtx - STARTING_FRAME_OFFSET
avoids creating add/sub of offset in nonlocal goto and setjmp. */
rtx avr_builtin_setjmp_frame_value (void)
{
return gen_rtx_MINUS (Pmode, virtual_stack_vars_rtx,
gen_int_mode (STARTING_FRAME_OFFSET, Pmode));
}
/* Return contents of MEM at frame pointer + stack size + 1 (+2 if 3 byte PC).
This is return address of function. */
rtx
avr_return_addr_rtx (int count, rtx tem)
{
rtx r;
/* Can only return this functions return address. Others not supported. */
if (count)
return NULL;
if (AVR_3_BYTE_PC)
{
r = gen_rtx_SYMBOL_REF (Pmode, ".L__stack_usage+2");
warning (0, "'builtin_return_address' contains only 2 bytes of address");
}
else
r = gen_rtx_SYMBOL_REF (Pmode, ".L__stack_usage+1");
r = gen_rtx_PLUS (Pmode, tem, r);
r = gen_frame_mem (Pmode, memory_address (Pmode, r));
r = gen_rtx_ROTATE (HImode, r, GEN_INT (8));
return r;
}
/* Return 1 if the function epilogue is just a single "ret". */
int
avr_simple_epilogue (void)
{
return (! frame_pointer_needed
&& get_frame_size () == 0
&& avr_regs_to_save (NULL) == 0
&& ! interrupt_function_p (current_function_decl)
&& ! signal_function_p (current_function_decl)
&& ! avr_naked_function_p (current_function_decl)
&& ! TREE_THIS_VOLATILE (current_function_decl));
}
/* This function checks sequence of live registers. */
static int
sequent_regs_live (void)
{
int reg;
int live_seq=0;
int cur_seq=0;
for (reg = 0; reg < 18; ++reg)
{
if (fixed_regs[reg])
{
/* Don't recognize sequences that contain global register
variables. */
if (live_seq != 0)
return 0;
else
continue;
}
if (!call_used_regs[reg])
{
if (df_regs_ever_live_p (reg))
{
++live_seq;
++cur_seq;
}
else
cur_seq = 0;
}
}
if (!frame_pointer_needed)
{
if (df_regs_ever_live_p (REG_Y))
{
++live_seq;
++cur_seq;
}
else
cur_seq = 0;
if (df_regs_ever_live_p (REG_Y+1))
{
++live_seq;
++cur_seq;
}
else
cur_seq = 0;
}
else
{
cur_seq += 2;
live_seq += 2;
}
return (cur_seq == live_seq) ? live_seq : 0;
}
/* Obtain the length sequence of insns. */
int
get_sequence_length (rtx insns)
{
rtx insn;
int length;
for (insn = insns, length = 0; insn; insn = NEXT_INSN (insn))
length += get_attr_length (insn);
return length;
}
/* Implement INCOMING_RETURN_ADDR_RTX. */
rtx
avr_incoming_return_addr_rtx (void)
{
/* The return address is at the top of the stack. Note that the push
was via post-decrement, which means the actual address is off by one. */
return gen_frame_mem (HImode, plus_constant (stack_pointer_rtx, 1));
}
/* Helper for expand_prologue. Emit a push of a byte register. */
static void
emit_push_byte (unsigned regno, bool frame_related_p)
{
rtx mem, reg, insn;
mem = gen_rtx_POST_DEC (HImode, stack_pointer_rtx);
mem = gen_frame_mem (QImode, mem);
reg = gen_rtx_REG (QImode, regno);
insn = emit_insn (gen_rtx_SET (VOIDmode, mem, reg));
if (frame_related_p)
RTX_FRAME_RELATED_P (insn) = 1;
cfun->machine->stack_usage++;
}
/* Output function prologue. */
void
expand_prologue (void)
{
int live_seq;
HARD_REG_SET set;
int minimize;
HOST_WIDE_INT size = get_frame_size();
rtx insn;
/* Init cfun->machine. */
cfun->machine->is_naked = avr_naked_function_p (current_function_decl);
cfun->machine->is_interrupt = interrupt_function_p (current_function_decl);
cfun->machine->is_signal = signal_function_p (current_function_decl);
cfun->machine->is_OS_task = avr_OS_task_function_p (current_function_decl);
cfun->machine->is_OS_main = avr_OS_main_function_p (current_function_decl);
cfun->machine->stack_usage = 0;
/* Prologue: naked. */
if (cfun->machine->is_naked)
{
return;
}
avr_regs_to_save (&set);
live_seq = sequent_regs_live ();
minimize = (TARGET_CALL_PROLOGUES
&& !cfun->machine->is_interrupt
&& !cfun->machine->is_signal
&& !cfun->machine->is_OS_task
&& !cfun->machine->is_OS_main
&& live_seq);
if (cfun->machine->is_interrupt || cfun->machine->is_signal)
{
/* Enable interrupts. */
if (cfun->machine->is_interrupt)
emit_insn (gen_enable_interrupt ());
/* Push zero reg. */
emit_push_byte (ZERO_REGNO, true);
/* Push tmp reg. */
emit_push_byte (TMP_REGNO, true);
/* Push SREG. */
/* ??? There's no dwarf2 column reserved for SREG. */
emit_move_insn (tmp_reg_rtx, gen_rtx_MEM (QImode, GEN_INT (SREG_ADDR)));
emit_push_byte (TMP_REGNO, false);
/* Push RAMPZ. */
/* ??? There's no dwarf2 column reserved for RAMPZ. */
if (AVR_HAVE_RAMPZ
&& TEST_HARD_REG_BIT (set, REG_Z)
&& TEST_HARD_REG_BIT (set, REG_Z + 1))
{
emit_move_insn (tmp_reg_rtx,
gen_rtx_MEM (QImode, GEN_INT (RAMPZ_ADDR)));
emit_push_byte (TMP_REGNO, false);
}
/* Clear zero reg. */
emit_move_insn (zero_reg_rtx, const0_rtx);
/* Prevent any attempt to delete the setting of ZERO_REG! */
emit_use (zero_reg_rtx);
}
if (minimize && (frame_pointer_needed
|| (AVR_2_BYTE_PC && live_seq > 6)
|| live_seq > 7))
{
int first_reg, reg, offset;
emit_move_insn (gen_rtx_REG (HImode, REG_X),
gen_int_mode (size, HImode));
insn = emit_insn (gen_call_prologue_saves
(gen_int_mode (live_seq, HImode),
gen_int_mode (size + live_seq, HImode)));
RTX_FRAME_RELATED_P (insn) = 1;
/* Describe the effect of the unspec_volatile call to prologue_saves.
Note that this formulation assumes that add_reg_note pushes the
notes to the front. Thus we build them in the reverse order of
how we want dwarf2out to process them. */
/* The function does always set frame_pointer_rtx, but whether that
is going to be permanent in the function is frame_pointer_needed. */
add_reg_note (insn, REG_CFA_ADJUST_CFA,
gen_rtx_SET (VOIDmode,
(frame_pointer_needed
? frame_pointer_rtx : stack_pointer_rtx),
plus_constant (stack_pointer_rtx,
-(size + live_seq))));
/* Note that live_seq always contains r28+r29, but the other
registers to be saved are all below 18. */
first_reg = 18 - (live_seq - 2);
for (reg = 29, offset = -live_seq + 1;
reg >= first_reg;
reg = (reg == 28 ? 17 : reg - 1), ++offset)
{
rtx m, r;
m = gen_rtx_MEM (QImode, plus_constant (stack_pointer_rtx, offset));
r = gen_rtx_REG (QImode, reg);
add_reg_note (insn, REG_CFA_OFFSET, gen_rtx_SET (VOIDmode, m, r));
}
cfun->machine->stack_usage += size + live_seq;
}
else
{
int reg;
for (reg = 0; reg < 32; ++reg)
if (TEST_HARD_REG_BIT (set, reg))
emit_push_byte (reg, true);
if (frame_pointer_needed)
{
if (!(cfun->machine->is_OS_task || cfun->machine->is_OS_main))
{
/* Push frame pointer. Always be consistent about the
ordering of pushes -- epilogue_restores expects the
register pair to be pushed low byte first. */
emit_push_byte (REG_Y, true);
emit_push_byte (REG_Y + 1, true);
}
if (!size)
{
insn = emit_move_insn (frame_pointer_rtx, stack_pointer_rtx);
RTX_FRAME_RELATED_P (insn) = 1;
}
else
{
/* Creating a frame can be done by direct manipulation of the
stack or via the frame pointer. These two methods are:
fp=sp
fp-=size
sp=fp
OR
sp-=size
fp=sp
the optimum method depends on function type, stack and frame size.
To avoid a complex logic, both methods are tested and shortest
is selected. */
rtx myfp;
rtx fp_plus_insns;
if (AVR_HAVE_8BIT_SP)
{
/* The high byte (r29) doesn't change. Prefer 'subi'
(1 cycle) over 'sbiw' (2 cycles, same size). */
myfp = gen_rtx_REG (QImode, FRAME_POINTER_REGNUM);
}
else
{
/* Normal sized addition. */
myfp = frame_pointer_rtx;
}
/* Method 1-Adjust frame pointer. */
start_sequence ();
/* Normally the dwarf2out frame-related-expr interpreter does
not expect to have the CFA change once the frame pointer is
set up. Thus we avoid marking the move insn below and
instead indicate that the entire operation is complete after
the frame pointer subtraction is done. */
emit_move_insn (frame_pointer_rtx, stack_pointer_rtx);
insn = emit_move_insn (myfp, plus_constant (myfp, -size));
RTX_FRAME_RELATED_P (insn) = 1;
add_reg_note (insn, REG_CFA_ADJUST_CFA,
gen_rtx_SET (VOIDmode, frame_pointer_rtx,
plus_constant (stack_pointer_rtx,
-size)));
/* Copy to stack pointer. Note that since we've already
changed the CFA to the frame pointer this operation
need not be annotated at all. */
if (AVR_HAVE_8BIT_SP)
{
emit_move_insn (stack_pointer_rtx, frame_pointer_rtx);
}
else if (TARGET_NO_INTERRUPTS
|| cfun->machine->is_signal
|| cfun->machine->is_OS_main)
{
emit_insn (gen_movhi_sp_r_irq_off (stack_pointer_rtx,
frame_pointer_rtx));
}
else if (cfun->machine->is_interrupt)
{
emit_insn (gen_movhi_sp_r_irq_on (stack_pointer_rtx,
frame_pointer_rtx));
}
else
{
emit_move_insn (stack_pointer_rtx, frame_pointer_rtx);
}
fp_plus_insns = get_insns ();
end_sequence ();
/* Method 2-Adjust Stack pointer. */
if (size <= 6)
{
rtx sp_plus_insns;
start_sequence ();
insn = plus_constant (stack_pointer_rtx, -size);
insn = emit_move_insn (stack_pointer_rtx, insn);
RTX_FRAME_RELATED_P (insn) = 1;
insn = emit_move_insn (frame_pointer_rtx, stack_pointer_rtx);
RTX_FRAME_RELATED_P (insn) = 1;
sp_plus_insns = get_insns ();
end_sequence ();
/* Use shortest method. */
if (get_sequence_length (sp_plus_insns)
< get_sequence_length (fp_plus_insns))
emit_insn (sp_plus_insns);
else
emit_insn (fp_plus_insns);
}
else
emit_insn (fp_plus_insns);
cfun->machine->stack_usage += size;
}
}
}
if (flag_stack_usage)
current_function_static_stack_size = cfun->machine->stack_usage;
}
/* Output summary at end of function prologue. */
static void
avr_asm_function_end_prologue (FILE *file)
{
if (cfun->machine->is_naked)
{
fputs ("/* prologue: naked */\n", file);
}
else
{
if (cfun->machine->is_interrupt)
{
fputs ("/* prologue: Interrupt */\n", file);
}
else if (cfun->machine->is_signal)
{
fputs ("/* prologue: Signal */\n", file);
}
else
fputs ("/* prologue: function */\n", file);
}
fprintf (file, "/* frame size = " HOST_WIDE_INT_PRINT_DEC " */\n",
get_frame_size());
fprintf (file, "/* stack size = %d */\n",
cfun->machine->stack_usage);
/* Create symbol stack offset here so all functions have it. Add 1 to stack
usage for offset so that SP + .L__stack_offset = return address. */
fprintf (file, ".L__stack_usage = %d\n", cfun->machine->stack_usage);
}
/* Implement EPILOGUE_USES. */
int
avr_epilogue_uses (int regno ATTRIBUTE_UNUSED)
{
if (reload_completed
&& cfun->machine
&& (cfun->machine->is_interrupt || cfun->machine->is_signal))
return 1;
return 0;
}
/* Helper for expand_epilogue. Emit a pop of a byte register. */
static void
emit_pop_byte (unsigned regno)
{
rtx mem, reg;
mem = gen_rtx_PRE_INC (HImode, stack_pointer_rtx);
mem = gen_frame_mem (QImode, mem);
reg = gen_rtx_REG (QImode, regno);
emit_insn (gen_rtx_SET (VOIDmode, reg, mem));
}
/* Output RTL epilogue. */
void
expand_epilogue (void)
{
int reg;
int live_seq;
HARD_REG_SET set;
int minimize;
HOST_WIDE_INT size = get_frame_size();
/* epilogue: naked */
if (cfun->machine->is_naked)
{
emit_jump_insn (gen_return ());
return;
}
avr_regs_to_save (&set);
live_seq = sequent_regs_live ();
minimize = (TARGET_CALL_PROLOGUES
&& !cfun->machine->is_interrupt
&& !cfun->machine->is_signal
&& !cfun->machine->is_OS_task
&& !cfun->machine->is_OS_main
&& live_seq);
if (minimize && (frame_pointer_needed || live_seq > 4))
{
if (frame_pointer_needed)
{
/* Get rid of frame. */
emit_move_insn(frame_pointer_rtx,
gen_rtx_PLUS (HImode, frame_pointer_rtx,
gen_int_mode (size, HImode)));
}
else
{
emit_move_insn (frame_pointer_rtx, stack_pointer_rtx);
}
emit_insn (gen_epilogue_restores (gen_int_mode (live_seq, HImode)));
}
else
{
if (frame_pointer_needed)
{
if (size)
{
/* Try two methods to adjust stack and select shortest. */
rtx myfp;
rtx fp_plus_insns;
if (AVR_HAVE_8BIT_SP)
{
/* The high byte (r29) doesn't change - prefer 'subi'
(1 cycle) over 'sbiw' (2 cycles, same size). */
myfp = gen_rtx_REG (QImode, FRAME_POINTER_REGNUM);
}
else
{
/* Normal sized addition. */
myfp = frame_pointer_rtx;
}
/* Method 1-Adjust frame pointer. */
start_sequence ();
emit_move_insn (myfp, plus_constant (myfp, size));
/* Copy to stack pointer. */
if (AVR_HAVE_8BIT_SP)
{
emit_move_insn (stack_pointer_rtx, frame_pointer_rtx);
}
else if (TARGET_NO_INTERRUPTS
|| cfun->machine->is_signal)
{
emit_insn (gen_movhi_sp_r_irq_off (stack_pointer_rtx,
frame_pointer_rtx));
}
else if (cfun->machine->is_interrupt)
{
emit_insn (gen_movhi_sp_r_irq_on (stack_pointer_rtx,
frame_pointer_rtx));
}
else
{
emit_move_insn (stack_pointer_rtx, frame_pointer_rtx);
}
fp_plus_insns = get_insns ();
end_sequence ();
/* Method 2-Adjust Stack pointer. */
if (size <= 5)
{
rtx sp_plus_insns;
start_sequence ();
emit_move_insn (stack_pointer_rtx,
plus_constant (stack_pointer_rtx, size));
sp_plus_insns = get_insns ();
end_sequence ();
/* Use shortest method. */
if (get_sequence_length (sp_plus_insns)
< get_sequence_length (fp_plus_insns))
emit_insn (sp_plus_insns);
else
emit_insn (fp_plus_insns);
}
else
emit_insn (fp_plus_insns);
}
if (!(cfun->machine->is_OS_task || cfun->machine->is_OS_main))
{
/* Restore previous frame_pointer. See expand_prologue for
rationale for not using pophi. */
emit_pop_byte (REG_Y + 1);
emit_pop_byte (REG_Y);
}
}
/* Restore used registers. */
for (reg = 31; reg >= 0; --reg)
if (TEST_HARD_REG_BIT (set, reg))
emit_pop_byte (reg);
if (cfun->machine->is_interrupt || cfun->machine->is_signal)
{
/* Restore RAMPZ using tmp reg as scratch. */
if (AVR_HAVE_RAMPZ
&& TEST_HARD_REG_BIT (set, REG_Z)
&& TEST_HARD_REG_BIT (set, REG_Z + 1))
{
emit_pop_byte (TMP_REGNO);
emit_move_insn (gen_rtx_MEM (QImode, GEN_INT (RAMPZ_ADDR)),
tmp_reg_rtx);
}
/* Restore SREG using tmp reg as scratch. */
emit_pop_byte (TMP_REGNO);
emit_move_insn (gen_rtx_MEM (QImode, GEN_INT (SREG_ADDR)),
tmp_reg_rtx);
/* Restore tmp REG. */
emit_pop_byte (TMP_REGNO);
/* Restore zero REG. */
emit_pop_byte (ZERO_REGNO);
}
emit_jump_insn (gen_return ());
}
}
/* Output summary messages at beginning of function epilogue. */
static void
avr_asm_function_begin_epilogue (FILE *file)
{
fprintf (file, "/* epilogue start */\n");
}
/* Implement TARGET_CANNOT_MODITY_JUMPS_P */
static bool
avr_cannot_modify_jumps_p (void)
{
/* Naked Functions must not have any instructions after
their epilogue, see PR42240 */
if (reload_completed
&& cfun->machine
&& cfun->machine->is_naked)
{
return true;
}
return false;
}
/* Return nonzero if X (an RTX) is a legitimate memory address on the target
machine for a memory operand of mode MODE. */
bool
avr_legitimate_address_p (enum machine_mode mode, rtx x, bool strict)
{
enum reg_class r = NO_REGS;
if (TARGET_ALL_DEBUG)
{
fprintf (stderr, "mode: (%s) %s %s %s %s:",
GET_MODE_NAME(mode),
strict ? "(strict)": "",
reload_completed ? "(reload_completed)": "",
reload_in_progress ? "(reload_in_progress)": "",
reg_renumber ? "(reg_renumber)" : "");
if (GET_CODE (x) == PLUS
&& REG_P (XEXP (x, 0))
&& GET_CODE (XEXP (x, 1)) == CONST_INT
&& INTVAL (XEXP (x, 1)) >= 0
&& INTVAL (XEXP (x, 1)) <= MAX_LD_OFFSET (mode)
&& reg_renumber
)
fprintf (stderr, "(r%d ---> r%d)", REGNO (XEXP (x, 0)),
true_regnum (XEXP (x, 0)));
debug_rtx (x);
}
if (REG_P (x) && (strict ? REG_OK_FOR_BASE_STRICT_P (x)
: REG_OK_FOR_BASE_NOSTRICT_P (x)))
r = POINTER_REGS;
else if (CONSTANT_ADDRESS_P (x))
r = ALL_REGS;
else if (GET_CODE (x) == PLUS
&& REG_P (XEXP (x, 0))
&& GET_CODE (XEXP (x, 1)) == CONST_INT
&& INTVAL (XEXP (x, 1)) >= 0)
{
int fit = INTVAL (XEXP (x, 1)) <= MAX_LD_OFFSET (mode);
if (fit)
{
if (! strict
|| REGNO (XEXP (x,0)) == REG_X
|| REGNO (XEXP (x,0)) == REG_Y
|| REGNO (XEXP (x,0)) == REG_Z)
r = BASE_POINTER_REGS;
if (XEXP (x,0) == frame_pointer_rtx
|| XEXP (x,0) == arg_pointer_rtx)
r = BASE_POINTER_REGS;
}
else if (frame_pointer_needed && XEXP (x,0) == frame_pointer_rtx)
r = POINTER_Y_REGS;
}
else if ((GET_CODE (x) == PRE_DEC || GET_CODE (x) == POST_INC)
&& REG_P (XEXP (x, 0))
&& (strict ? REG_OK_FOR_BASE_STRICT_P (XEXP (x, 0))
: REG_OK_FOR_BASE_NOSTRICT_P (XEXP (x, 0))))
{
r = POINTER_REGS;
}
if (TARGET_ALL_DEBUG)
{
fprintf (stderr, " ret = %c\n", r + '0');
}
return r == NO_REGS ? 0 : (int)r;
}
/* Attempts to replace X with a valid
memory address for an operand of mode MODE */
rtx
avr_legitimize_address (rtx x, rtx oldx, enum machine_mode mode)
{
x = oldx;
if (TARGET_ALL_DEBUG)
{
fprintf (stderr, "legitimize_address mode: %s", GET_MODE_NAME(mode));
debug_rtx (oldx);
}
if (GET_CODE (oldx) == PLUS
&& REG_P (XEXP (oldx,0)))
{
if (REG_P (XEXP (oldx,1)))
x = force_reg (GET_MODE (oldx), oldx);
else if (GET_CODE (XEXP (oldx, 1)) == CONST_INT)
{
int offs = INTVAL (XEXP (oldx,1));
if (frame_pointer_rtx != XEXP (oldx,0))
if (offs > MAX_LD_OFFSET (mode))
{
if (TARGET_ALL_DEBUG)
fprintf (stderr, "force_reg (big offset)\n");
x = force_reg (GET_MODE (oldx), oldx);
}
}
}
return x;
}
/* Return a pointer register name as a string. */
static const char *
ptrreg_to_str (int regno)
{
switch (regno)
{
case REG_X: return "X";
case REG_Y: return "Y";
case REG_Z: return "Z";
default:
output_operand_lossage ("address operand requires constraint for X, Y, or Z register");
}
return NULL;
}
/* Return the condition name as a string.
Used in conditional jump constructing */
static const char *
cond_string (enum rtx_code code)
{
switch (code)
{
case NE:
return "ne";
case EQ:
return "eq";
case GE:
if (cc_prev_status.flags & CC_OVERFLOW_UNUSABLE)
return "pl";
else
return "ge";
case LT:
if (cc_prev_status.flags & CC_OVERFLOW_UNUSABLE)
return "mi";
else
return "lt";
case GEU:
return "sh";
case LTU:
return "lo";
default:
gcc_unreachable ();
}
}
/* Output ADDR to FILE as address. */
void
print_operand_address (FILE *file, rtx addr)
{
switch (GET_CODE (addr))
{
case REG:
fprintf (file, ptrreg_to_str (REGNO (addr)));
break;
case PRE_DEC:
fprintf (file, "-%s", ptrreg_to_str (REGNO (XEXP (addr, 0))));
break;
case POST_INC:
fprintf (file, "%s+", ptrreg_to_str (REGNO (XEXP (addr, 0))));
break;
default:
if (CONSTANT_ADDRESS_P (addr)
&& text_segment_operand (addr, VOIDmode))
{
rtx x = XEXP (addr,0);
if (GET_CODE (x) == PLUS && GET_CODE (XEXP (x,1)) == CONST_INT)
{
/* Assembler gs() will implant word address. Make offset
a byte offset inside gs() for assembler. This is
needed because the more logical (constant+gs(sym)) is not
accepted by gas. For 128K and lower devices this is ok. For
large devices it will create a Trampoline to offset from symbol
which may not be what the user really wanted. */
fprintf (file, "gs(");
output_addr_const (file, XEXP (x,0));
fprintf (file,"+" HOST_WIDE_INT_PRINT_DEC ")", 2 * INTVAL (XEXP (x,1)));
if (AVR_3_BYTE_PC)
if (warning (0, "pointer offset from symbol maybe incorrect"))
{
output_addr_const (stderr, addr);
fprintf(stderr,"\n");
}
}
else
{
fprintf (file, "gs(");
output_addr_const (file, addr);
fprintf (file, ")");
}
}
else
output_addr_const (file, addr);
}
}
/* Output X as assembler operand to file FILE. */
void
print_operand (FILE *file, rtx x, int code)
{
int abcd = 0;
if (code >= 'A' && code <= 'D')
abcd = code - 'A';
if (code == '~')
{
if (!AVR_HAVE_JMP_CALL)
fputc ('r', file);
}
else if (code == '!')
{
if (AVR_HAVE_EIJMP_EICALL)
fputc ('e', file);
}
else if (REG_P (x))
{
if (x == zero_reg_rtx)
fprintf (file, "__zero_reg__");
else
fprintf (file, reg_names[true_regnum (x) + abcd]);
}
else if (GET_CODE (x) == CONST_INT)
fprintf (file, HOST_WIDE_INT_PRINT_DEC, INTVAL (x) + abcd);
else if (GET_CODE (x) == MEM)
{
rtx addr = XEXP (x,0);
if (code == 'm')
{
if (!CONSTANT_P (addr))
fatal_insn ("bad address, not a constant):", addr);
/* Assembler template with m-code is data - not progmem section */
if (text_segment_operand (addr, VOIDmode))
if (warning ( 0, "accessing data memory with program memory address"))
{
output_addr_const (stderr, addr);
fprintf(stderr,"\n");
}
output_addr_const (file, addr);
}
else if (code == 'o')
{
if (GET_CODE (addr) != PLUS)
fatal_insn ("bad address, not (reg+disp):", addr);
print_operand (file, XEXP (addr, 1), 0);
}
else if (code == 'p' || code == 'r')
{
if (GET_CODE (addr) != POST_INC && GET_CODE (addr) != PRE_DEC)
fatal_insn ("bad address, not post_inc or pre_dec:", addr);
if (code == 'p')
print_operand_address (file, XEXP (addr, 0)); /* X, Y, Z */
else
print_operand (file, XEXP (addr, 0), 0); /* r26, r28, r30 */
}
else if (GET_CODE (addr) == PLUS)
{
print_operand_address (file, XEXP (addr,0));
if (REGNO (XEXP (addr, 0)) == REG_X)
fatal_insn ("internal compiler error. Bad address:"
,addr);
fputc ('+', file);
print_operand (file, XEXP (addr,1), code);
}
else
print_operand_address (file, addr);
}
else if (code == 'x')
{
/* Constant progmem address - like used in jmp or call */
if (0 == text_segment_operand (x, VOIDmode))
if (warning ( 0, "accessing program memory with data memory address"))
{
output_addr_const (stderr, x);
fprintf(stderr,"\n");
}
/* Use normal symbol for direct address no linker trampoline needed */
output_addr_const (file, x);
}
else if (GET_CODE (x) == CONST_DOUBLE)
{
long val;
REAL_VALUE_TYPE rv;
if (GET_MODE (x) != SFmode)
fatal_insn ("internal compiler error. Unknown mode:", x);
REAL_VALUE_FROM_CONST_DOUBLE (rv, x);
REAL_VALUE_TO_TARGET_SINGLE (rv, val);
fprintf (file, "0x%lx", val);
}
else if (code == 'j')
fputs (cond_string (GET_CODE (x)), file);
else if (code == 'k')
fputs (cond_string (reverse_condition (GET_CODE (x))), file);
else
print_operand_address (file, x);
}
/* Update the condition code in the INSN. */
void
notice_update_cc (rtx body ATTRIBUTE_UNUSED, rtx insn)
{
rtx set;
switch (get_attr_cc (insn))
{
case CC_NONE:
/* Insn does not affect CC at all. */
break;
case CC_SET_N:
CC_STATUS_INIT;
break;
case CC_SET_ZN:
set = single_set (insn);
CC_STATUS_INIT;
if (set)
{
cc_status.flags |= CC_NO_OVERFLOW;
cc_status.value1 = SET_DEST (set);
}
break;
case CC_SET_CZN:
/* Insn sets the Z,N,C flags of CC to recog_operand[0].
The V flag may or may not be known but that's ok because
alter_cond will change tests to use EQ/NE. */
set = single_set (insn);
CC_STATUS_INIT;
if (set)
{
cc_status.value1 = SET_DEST (set);
cc_status.flags |= CC_OVERFLOW_UNUSABLE;
}
break;
case CC_COMPARE:
set = single_set (insn);
CC_STATUS_INIT;
if (set)
cc_status.value1 = SET_SRC (set);
break;
case CC_CLOBBER:
/* Insn doesn't leave CC in a usable state. */
CC_STATUS_INIT;
/* Correct CC for the ashrqi3 with the shift count as CONST_INT != 6 */
set = single_set (insn);
if (set)
{
rtx src = SET_SRC (set);
if (GET_CODE (src) == ASHIFTRT
&& GET_MODE (src) == QImode)
{
rtx x = XEXP (src, 1);
if (CONST_INT_P (x)
&& IN_RANGE (INTVAL (x), 1, 5))
{
cc_status.value1 = SET_DEST (set);
cc_status.flags |= CC_OVERFLOW_UNUSABLE;
}
}
}
break;
}
}
/* Return maximum number of consecutive registers of
class CLASS needed to hold a value of mode MODE. */
int
class_max_nregs (enum reg_class rclass ATTRIBUTE_UNUSED,enum machine_mode mode)
{
return ((GET_MODE_SIZE (mode) + UNITS_PER_WORD - 1) / UNITS_PER_WORD);
}
/* Choose mode for jump insn:
1 - relative jump in range -63 <= x <= 62 ;
2 - relative jump in range -2046 <= x <= 2045 ;
3 - absolute jump (only for ATmega[16]03). */
int
avr_jump_mode (rtx x, rtx insn)
{
int dest_addr = INSN_ADDRESSES (INSN_UID (GET_CODE (x) == LABEL_REF
? XEXP (x, 0) : x));
int cur_addr = INSN_ADDRESSES (INSN_UID (insn));
int jump_distance = cur_addr - dest_addr;
if (-63 <= jump_distance && jump_distance <= 62)
return 1;
else if (-2046 <= jump_distance && jump_distance <= 2045)
return 2;
else if (AVR_HAVE_JMP_CALL)
return 3;
return 2;
}
/* return an AVR condition jump commands.
X is a comparison RTX.
LEN is a number returned by avr_jump_mode function.
if REVERSE nonzero then condition code in X must be reversed. */
const char *
ret_cond_branch (rtx x, int len, int reverse)
{
RTX_CODE cond = reverse ? reverse_condition (GET_CODE (x)) : GET_CODE (x);
switch (cond)
{
case GT:
if (cc_prev_status.flags & CC_OVERFLOW_UNUSABLE)
return (len == 1 ? (AS1 (breq,.+2) CR_TAB
AS1 (brpl,%0)) :
len == 2 ? (AS1 (breq,.+4) CR_TAB
AS1 (brmi,.+2) CR_TAB
AS1 (rjmp,%0)) :
(AS1 (breq,.+6) CR_TAB
AS1 (brmi,.+4) CR_TAB
AS1 (jmp,%0)));
else
return (len == 1 ? (AS1 (breq,.+2) CR_TAB
AS1 (brge,%0)) :
len == 2 ? (AS1 (breq,.+4) CR_TAB
AS1 (brlt,.+2) CR_TAB
AS1 (rjmp,%0)) :
(AS1 (breq,.+6) CR_TAB
AS1 (brlt,.+4) CR_TAB
AS1 (jmp,%0)));
case GTU:
return (len == 1 ? (AS1 (breq,.+2) CR_TAB
AS1 (brsh,%0)) :
len == 2 ? (AS1 (breq,.+4) CR_TAB
AS1 (brlo,.+2) CR_TAB
AS1 (rjmp,%0)) :
(AS1 (breq,.+6) CR_TAB
AS1 (brlo,.+4) CR_TAB
AS1 (jmp,%0)));
case LE:
if (cc_prev_status.flags & CC_OVERFLOW_UNUSABLE)
return (len == 1 ? (AS1 (breq,%0) CR_TAB
AS1 (brmi,%0)) :
len == 2 ? (AS1 (breq,.+2) CR_TAB
AS1 (brpl,.+2) CR_TAB
AS1 (rjmp,%0)) :
(AS1 (breq,.+2) CR_TAB
AS1 (brpl,.+4) CR_TAB
AS1 (jmp,%0)));
else
return (len == 1 ? (AS1 (breq,%0) CR_TAB
AS1 (brlt,%0)) :
len == 2 ? (AS1 (breq,.+2) CR_TAB
AS1 (brge,.+2) CR_TAB
AS1 (rjmp,%0)) :
(AS1 (breq,.+2) CR_TAB
AS1 (brge,.+4) CR_TAB
AS1 (jmp,%0)));
case LEU:
return (len == 1 ? (AS1 (breq,%0) CR_TAB
AS1 (brlo,%0)) :
len == 2 ? (AS1 (breq,.+2) CR_TAB
AS1 (brsh,.+2) CR_TAB
AS1 (rjmp,%0)) :
(AS1 (breq,.+2) CR_TAB
AS1 (brsh,.+4) CR_TAB
AS1 (jmp,%0)));
default:
if (reverse)
{
switch (len)
{
case 1:
return AS1 (br%k1,%0);
case 2:
return (AS1 (br%j1,.+2) CR_TAB
AS1 (rjmp,%0));
default:
return (AS1 (br%j1,.+4) CR_TAB
AS1 (jmp,%0));
}
}
else
{
switch (len)
{
case 1:
return AS1 (br%j1,%0);
case 2:
return (AS1 (br%k1,.+2) CR_TAB
AS1 (rjmp,%0));
default:
return (AS1 (br%k1,.+4) CR_TAB
AS1 (jmp,%0));
}
}
}
return "";
}
/* Predicate function for immediate operand which fits to byte (8bit) */
int
byte_immediate_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED)
{
return (GET_CODE (op) == CONST_INT
&& INTVAL (op) <= 0xff && INTVAL (op) >= 0);
}
/* Output insn cost for next insn. */
void
final_prescan_insn (rtx insn, rtx *operand ATTRIBUTE_UNUSED,
int num_operands ATTRIBUTE_UNUSED)
{
if (TARGET_ALL_DEBUG)
{
fprintf (asm_out_file, "/* DEBUG: cost = %d. */\n",
rtx_cost (PATTERN (insn), INSN, !optimize_size));
}
}
/* Return 0 if undefined, 1 if always true or always false. */
int
avr_simplify_comparison_p (enum machine_mode mode, RTX_CODE op, rtx x)
{
unsigned int max = (mode == QImode ? 0xff :
mode == HImode ? 0xffff :
mode == SImode ? 0xffffffff : 0);
if (max && op && GET_CODE (x) == CONST_INT)
{
if (unsigned_condition (op) != op)
max >>= 1;
if (max != (INTVAL (x) & max)
&& INTVAL (x) != 0xff)
return 1;
}
return 0;
}
/* Returns nonzero if REGNO is the number of a hard
register in which function arguments are sometimes passed. */
int
function_arg_regno_p(int r)
{
return (r >= 8 && r <= 25);
}
/* Initializing the variable cum for the state at the beginning
of the argument list. */
void
init_cumulative_args (CUMULATIVE_ARGS *cum, tree fntype, rtx libname,
tree fndecl ATTRIBUTE_UNUSED)
{
cum->nregs = 18;
cum->regno = FIRST_CUM_REG;
if (!libname && stdarg_p (fntype))
cum->nregs = 0;
}
/* Returns the number of registers to allocate for a function argument. */
static int
avr_num_arg_regs (enum machine_mode mode, const_tree type)
{
int size;
if (mode == BLKmode)
size = int_size_in_bytes (type);
else
size = GET_MODE_SIZE (mode);
/* Align all function arguments to start in even-numbered registers.
Odd-sized arguments leave holes above them. */
return (size + 1) & ~1;
}
/* Controls whether a function argument is passed
in a register, and which register. */
static rtx
avr_function_arg (CUMULATIVE_ARGS *cum, enum machine_mode mode,
const_tree type, bool named ATTRIBUTE_UNUSED)
{
int bytes = avr_num_arg_regs (mode, type);
if (cum->nregs && bytes <= cum->nregs)
return gen_rtx_REG (mode, cum->regno - bytes);
return NULL_RTX;
}
/* Update the summarizer variable CUM to advance past an argument
in the argument list. */
static void
avr_function_arg_advance (CUMULATIVE_ARGS *cum, enum machine_mode mode,
const_tree type, bool named ATTRIBUTE_UNUSED)
{
int bytes = avr_num_arg_regs (mode, type);
cum->nregs -= bytes;
cum->regno -= bytes;
if (cum->nregs <= 0)
{
cum->nregs = 0;
cum->regno = FIRST_CUM_REG;
}
}
/***********************************************************************
Functions for outputting various mov's for a various modes
************************************************************************/
const char *
output_movqi (rtx insn, rtx operands[], int *l)
{
int dummy;
rtx dest = operands[0];
rtx src = operands[1];
int *real_l = l;
if (!l)
l = &dummy;
*l = 1;
if (register_operand (dest, QImode))
{
if (register_operand (src, QImode)) /* mov r,r */
{
if (test_hard_reg_class (STACK_REG, dest))
return AS2 (out,%0,%1);
else if (test_hard_reg_class (STACK_REG, src))
return AS2 (in,%0,%1);
return AS2 (mov,%0,%1);
}
else if (CONSTANT_P (src))
{
if (test_hard_reg_class (LD_REGS, dest)) /* ldi d,i */
return AS2 (ldi,%0,lo8(%1));
if (GET_CODE (src) == CONST_INT)
{
if (src == const0_rtx) /* mov r,L */
return AS1 (clr,%0);
else if (src == const1_rtx)
{
*l = 2;
return (AS1 (clr,%0) CR_TAB
AS1 (inc,%0));
}
else if (src == constm1_rtx)
{
/* Immediate constants -1 to any register */
*l = 2;
return (AS1 (clr,%0) CR_TAB
AS1 (dec,%0));
}
else
{
int bit_nr = exact_log2 (INTVAL (src));
if (bit_nr >= 0)
{
*l = 3;
if (!real_l)
output_asm_insn ((AS1 (clr,%0) CR_TAB
"set"), operands);
if (!real_l)
avr_output_bld (operands, bit_nr);
return "";
}
}
}
/* Last resort, larger than loading from memory. */
*l = 4;
return (AS2 (mov,__tmp_reg__,r31) CR_TAB
AS2 (ldi,r31,lo8(%1)) CR_TAB
AS2 (mov,%0,r31) CR_TAB
AS2 (mov,r31,__tmp_reg__));
}
else if (GET_CODE (src) == MEM)
return out_movqi_r_mr (insn, operands, real_l); /* mov r,m */
}
else if (GET_CODE (dest) == MEM)
{
const char *templ;
if (src == const0_rtx)
operands[1] = zero_reg_rtx;
templ = out_movqi_mr_r (insn, operands, real_l);
if (!real_l)
output_asm_insn (templ, operands);
operands[1] = src;
}
return "";
}
const char *
output_movhi (rtx insn, rtx operands[], int *l)
{
int dummy;
rtx dest = operands[0];
rtx src = operands[1];
int *real_l = l;
if (!l)
l = &dummy;
if (register_operand (dest, HImode))
{
if (register_operand (src, HImode)) /* mov r,r */
{
if (test_hard_reg_class (STACK_REG, dest))
{
if (AVR_HAVE_8BIT_SP)
return *l = 1, AS2 (out,__SP_L__,%A1);
/* Use simple load of stack pointer if no interrupts are
used. */
else if (TARGET_NO_INTERRUPTS)
return *l = 2, (AS2 (out,__SP_H__,%B1) CR_TAB
AS2 (out,__SP_L__,%A1));
*l = 5;
return (AS2 (in,__tmp_reg__,__SREG__) CR_TAB
"cli" CR_TAB
AS2 (out,__SP_H__,%B1) CR_TAB
AS2 (out,__SREG__,__tmp_reg__) CR_TAB
AS2 (out,__SP_L__,%A1));
}
else if (test_hard_reg_class (STACK_REG, src))
{
*l = 2;
return AVR_HAVE_8BIT_SP
? (AS2 (in,%A0,__SP_L__) CR_TAB
AS1 (clr,%B0))
: (AS2 (in,%A0,__SP_L__) CR_TAB
AS2 (in,%B0,__SP_H__));
}
if (AVR_HAVE_MOVW)
{
*l = 1;
return (AS2 (movw,%0,%1));
}
else
{
*l = 2;
return (AS2 (mov,%A0,%A1) CR_TAB
AS2 (mov,%B0,%B1));
}
}
else if (CONSTANT_P (src))
{
if (test_hard_reg_class (LD_REGS, dest)) /* ldi d,i */
{
*l = 2;
return (AS2 (ldi,%A0,lo8(%1)) CR_TAB
AS2 (ldi,%B0,hi8(%1)));
}
if (GET_CODE (src) == CONST_INT)
{
if (src == const0_rtx) /* mov r,L */
{
*l = 2;
return (AS1 (clr,%A0) CR_TAB
AS1 (clr,%B0));
}
else if (src == const1_rtx)
{
*l = 3;
return (AS1 (clr,%A0) CR_TAB
AS1 (clr,%B0) CR_TAB
AS1 (inc,%A0));
}
else if (src == constm1_rtx)
{
/* Immediate constants -1 to any register */
*l = 3;
return (AS1 (clr,%0) CR_TAB
AS1 (dec,%A0) CR_TAB
AS2 (mov,%B0,%A0));
}
else
{
int bit_nr = exact_log2 (INTVAL (src));
if (bit_nr >= 0)
{
*l = 4;
if (!real_l)
output_asm_insn ((AS1 (clr,%A0) CR_TAB
AS1 (clr,%B0) CR_TAB
"set"), operands);
if (!real_l)
avr_output_bld (operands, bit_nr);
return "";
}
}
if ((INTVAL (src) & 0xff) == 0)
{
*l = 5;
return (AS2 (mov,__tmp_reg__,r31) CR_TAB
AS1 (clr,%A0) CR_TAB
AS2 (ldi,r31,hi8(%1)) CR_TAB
AS2 (mov,%B0,r31) CR_TAB
AS2 (mov,r31,__tmp_reg__));
}
else if ((INTVAL (src) & 0xff00) == 0)
{
*l = 5;
return (AS2 (mov,__tmp_reg__,r31) CR_TAB
AS2 (ldi,r31,lo8(%1)) CR_TAB
AS2 (mov,%A0,r31) CR_TAB
AS1 (clr,%B0) CR_TAB
AS2 (mov,r31,__tmp_reg__));
}
}
/* Last resort, equal to loading from memory. */
*l = 6;
return (AS2 (mov,__tmp_reg__,r31) CR_TAB
AS2 (ldi,r31,lo8(%1)) CR_TAB
AS2 (mov,%A0,r31) CR_TAB
AS2 (ldi,r31,hi8(%1)) CR_TAB
AS2 (mov,%B0,r31) CR_TAB
AS2 (mov,r31,__tmp_reg__));
}
else if (GET_CODE (src) == MEM)
return out_movhi_r_mr (insn, operands, real_l); /* mov r,m */
}
else if (GET_CODE (dest) == MEM)
{
const char *templ;
if (src == const0_rtx)
operands[1] = zero_reg_rtx;
templ = out_movhi_mr_r (insn, operands, real_l);
if (!real_l)
output_asm_insn (templ, operands);
operands[1] = src;
return "";
}
fatal_insn ("invalid insn:", insn);
return "";
}
const char *
out_movqi_r_mr (rtx insn, rtx op[], int *l)
{
rtx dest = op[0];
rtx src = op[1];
rtx x = XEXP (src, 0);
int dummy;
if (!l)
l = &dummy;
if (CONSTANT_ADDRESS_P (x))
{
if (CONST_INT_P (x) && INTVAL (x) == SREG_ADDR)
{
*l = 1;
return AS2 (in,%0,__SREG__);
}
if (optimize > 0 && io_address_operand (x, QImode))
{
*l = 1;
return AS2 (in,%0,%m1-0x20);
}
*l = 2;
return AS2 (lds,%0,%m1);
}
/* memory access by reg+disp */
else if (GET_CODE (x) == PLUS
&& REG_P (XEXP (x,0))
&& GET_CODE (XEXP (x,1)) == CONST_INT)
{
if ((INTVAL (XEXP (x,1)) - GET_MODE_SIZE (GET_MODE (src))) >= 63)
{
int disp = INTVAL (XEXP (x,1));
if (REGNO (XEXP (x,0)) != REG_Y)
fatal_insn ("incorrect insn:",insn);
if (disp <= 63 + MAX_LD_OFFSET (GET_MODE (src)))
return *l = 3, (AS2 (adiw,r28,%o1-63) CR_TAB
AS2 (ldd,%0,Y+63) CR_TAB
AS2 (sbiw,r28,%o1-63));
return *l = 5, (AS2 (subi,r28,lo8(-%o1)) CR_TAB
AS2 (sbci,r29,hi8(-%o1)) CR_TAB
AS2 (ld,%0,Y) CR_TAB
AS2 (subi,r28,lo8(%o1)) CR_TAB
AS2 (sbci,r29,hi8(%o1)));
}
else if (REGNO (XEXP (x,0)) == REG_X)
{
/* This is a paranoid case LEGITIMIZE_RELOAD_ADDRESS must exclude
it but I have this situation with extremal optimizing options. */
if (reg_overlap_mentioned_p (dest, XEXP (x,0))
|| reg_unused_after (insn, XEXP (x,0)))
return *l = 2, (AS2 (adiw,r26,%o1) CR_TAB
AS2 (ld,%0,X));
return *l = 3, (AS2 (adiw,r26,%o1) CR_TAB
AS2 (ld,%0,X) CR_TAB
AS2 (sbiw,r26,%o1));
}
*l = 1;
return AS2 (ldd,%0,%1);
}
*l = 1;
return AS2 (ld,%0,%1);
}
const char *
out_movhi_r_mr (rtx insn, rtx op[], int *l)
{
rtx dest = op[0];
rtx src = op[1];
rtx base = XEXP (src, 0);
int reg_dest = true_regnum (dest);
int reg_base = true_regnum (base);
/* "volatile" forces reading low byte first, even if less efficient,
for correct operation with 16-bit I/O registers. */
int mem_volatile_p = MEM_VOLATILE_P (src);
int tmp;
if (!l)
l = &tmp;
if (reg_base > 0)
{
if (reg_dest == reg_base) /* R = (R) */
{
*l = 3;
return (AS2 (ld,__tmp_reg__,%1+) CR_TAB
AS2 (ld,%B0,%1) CR_TAB
AS2 (mov,%A0,__tmp_reg__));
}
else if (reg_base == REG_X) /* (R26) */
{
if (reg_unused_after (insn, base))
{
*l = 2;
return (AS2 (ld,%A0,X+) CR_TAB
AS2 (ld,%B0,X));
}
*l = 3;
return (AS2 (ld,%A0,X+) CR_TAB
AS2 (ld,%B0,X) CR_TAB
AS2 (sbiw,r26,1));
}
else /* (R) */
{
*l = 2;
return (AS2 (ld,%A0,%1) CR_TAB
AS2 (ldd,%B0,%1+1));
}
}
else if (GET_CODE (base) == PLUS) /* (R + i) */
{
int disp = INTVAL (XEXP (base, 1));
int reg_base = true_regnum (XEXP (base, 0));
if (disp > MAX_LD_OFFSET (GET_MODE (src)))
{
if (REGNO (XEXP (base, 0)) != REG_Y)
fatal_insn ("incorrect insn:",insn);
if (disp <= 63 + MAX_LD_OFFSET (GET_MODE (src)))
return *l = 4, (AS2 (adiw,r28,%o1-62) CR_TAB
AS2 (ldd,%A0,Y+62) CR_TAB
AS2 (ldd,%B0,Y+63) CR_TAB
AS2 (sbiw,r28,%o1-62));
return *l = 6, (AS2 (subi,r28,lo8(-%o1)) CR_TAB
AS2 (sbci,r29,hi8(-%o1)) CR_TAB
AS2 (ld,%A0,Y) CR_TAB
AS2 (ldd,%B0,Y+1) CR_TAB
AS2 (subi,r28,lo8(%o1)) CR_TAB
AS2 (sbci,r29,hi8(%o1)));
}
if (reg_base == REG_X)
{
/* This is a paranoid case. LEGITIMIZE_RELOAD_ADDRESS must exclude
it but I have this situation with extremal
optimization options. */
*l = 4;
if (reg_base == reg_dest)
return (AS2 (adiw,r26,%o1) CR_TAB
AS2 (ld,__tmp_reg__,X+) CR_TAB
AS2 (ld,%B0,X) CR_TAB
AS2 (mov,%A0,__tmp_reg__));
return (AS2 (adiw,r26,%o1) CR_TAB
AS2 (ld,%A0,X+) CR_TAB
AS2 (ld,%B0,X) CR_TAB
AS2 (sbiw,r26,%o1+1));
}
if (reg_base == reg_dest)
{
*l = 3;
return (AS2 (ldd,__tmp_reg__,%A1) CR_TAB
AS2 (ldd,%B0,%B1) CR_TAB
AS2 (mov,%A0,__tmp_reg__));
}
*l = 2;
return (AS2 (ldd,%A0,%A1) CR_TAB
AS2 (ldd,%B0,%B1));
}
else if (GET_CODE (base) == PRE_DEC) /* (--R) */
{
if (reg_overlap_mentioned_p (dest, XEXP (base, 0)))
fatal_insn ("incorrect insn:", insn);
if (mem_volatile_p)
{
if (REGNO (XEXP (base, 0)) == REG_X)
{
*l = 4;
return (AS2 (sbiw,r26,2) CR_TAB
AS2 (ld,%A0,X+) CR_TAB
AS2 (ld,%B0,X) CR_TAB
AS2 (sbiw,r26,1));
}
else
{
*l = 3;
return (AS2 (sbiw,%r1,2) CR_TAB
AS2 (ld,%A0,%p1) CR_TAB
AS2 (ldd,%B0,%p1+1));
}
}
*l = 2;
return (AS2 (ld,%B0,%1) CR_TAB
AS2 (ld,%A0,%1));
}
else if (GET_CODE (base) == POST_INC) /* (R++) */
{
if (reg_overlap_mentioned_p (dest, XEXP (base, 0)))
fatal_insn ("incorrect insn:", insn);
*l = 2;
return (AS2 (ld,%A0,%1) CR_TAB
AS2 (ld,%B0,%1));
}
else if (CONSTANT_ADDRESS_P (base))
{
if (optimize > 0 && io_address_operand (base, HImode))
{
*l = 2;
return (AS2 (in,%A0,%m1-0x20) CR_TAB
AS2 (in,%B0,%m1+1-0x20));
}
*l = 4;
return (AS2 (lds,%A0,%m1) CR_TAB
AS2 (lds,%B0,%m1+1));
}
fatal_insn ("unknown move insn:",insn);
return "";
}
const char *
out_movsi_r_mr (rtx insn, rtx op[], int *l)
{
rtx dest = op[0];
rtx src = op[1];
rtx base = XEXP (src, 0);
int reg_dest = true_regnum (dest);
int reg_base = true_regnum (base);
int tmp;
if (!l)
l = &tmp;
if (reg_base > 0)
{
if (reg_base == REG_X) /* (R26) */
{
if (reg_dest == REG_X)
/* "ld r26,-X" is undefined */
return *l=7, (AS2 (adiw,r26,3) CR_TAB
AS2 (ld,r29,X) CR_TAB
AS2 (ld,r28,-X) CR_TAB
AS2 (ld,__tmp_reg__,-X) CR_TAB
AS2 (sbiw,r26,1) CR_TAB
AS2 (ld,r26,X) CR_TAB
AS2 (mov,r27,__tmp_reg__));
else if (reg_dest == REG_X - 2)
return *l=5, (AS2 (ld,%A0,X+) CR_TAB
AS2 (ld,%B0,X+) CR_TAB
AS2 (ld,__tmp_reg__,X+) CR_TAB
AS2 (ld,%D0,X) CR_TAB
AS2 (mov,%C0,__tmp_reg__));
else if (reg_unused_after (insn, base))
return *l=4, (AS2 (ld,%A0,X+) CR_TAB
AS2 (ld,%B0,X+) CR_TAB
AS2 (ld,%C0,X+) CR_TAB
AS2 (ld,%D0,X));
else
return *l=5, (AS2 (ld,%A0,X+) CR_TAB
AS2 (ld,%B0,X+) CR_TAB
AS2 (ld,%C0,X+) CR_TAB
AS2 (ld,%D0,X) CR_TAB
AS2 (sbiw,r26,3));
}
else
{
if (reg_dest == reg_base)
return *l=5, (AS2 (ldd,%D0,%1+3) CR_TAB
AS2 (ldd,%C0,%1+2) CR_TAB
AS2 (ldd,__tmp_reg__,%1+1) CR_TAB
AS2 (ld,%A0,%1) CR_TAB
AS2 (mov,%B0,__tmp_reg__));
else if (reg_base == reg_dest + 2)
return *l=5, (AS2 (ld ,%A0,%1) CR_TAB
AS2 (ldd,%B0,%1+1) CR_TAB
AS2 (ldd,__tmp_reg__,%1+2) CR_TAB
AS2 (ldd,%D0,%1+3) CR_TAB
AS2 (mov,%C0,__tmp_reg__));
else
return *l=4, (AS2 (ld ,%A0,%1) CR_TAB
AS2 (ldd,%B0,%1+1) CR_TAB
AS2 (ldd,%C0,%1+2) CR_TAB
AS2 (ldd,%D0,%1+3));
}
}
else if (GET_CODE (base) == PLUS) /* (R + i) */
{
int disp = INTVAL (XEXP (base, 1));
if (disp > MAX_LD_OFFSET (GET_MODE (src)))
{
if (REGNO (XEXP (base, 0)) != REG_Y)
fatal_insn ("incorrect insn:",insn);
if (disp <= 63 + MAX_LD_OFFSET (GET_MODE (src)))
return *l = 6, (AS2 (adiw,r28,%o1-60) CR_TAB
AS2 (ldd,%A0,Y+60) CR_TAB
AS2 (ldd,%B0,Y+61) CR_TAB
AS2 (ldd,%C0,Y+62) CR_TAB
AS2 (ldd,%D0,Y+63) CR_TAB
AS2 (sbiw,r28,%o1-60));
return *l = 8, (AS2 (subi,r28,lo8(-%o1)) CR_TAB
AS2 (sbci,r29,hi8(-%o1)) CR_TAB
AS2 (ld,%A0,Y) CR_TAB
AS2 (ldd,%B0,Y+1) CR_TAB
AS2 (ldd,%C0,Y+2) CR_TAB
AS2 (ldd,%D0,Y+3) CR_TAB
AS2 (subi,r28,lo8(%o1)) CR_TAB
AS2 (sbci,r29,hi8(%o1)));
}
reg_base = true_regnum (XEXP (base, 0));
if (reg_base == REG_X)
{
/* R = (X + d) */
if (reg_dest == REG_X)
{
*l = 7;
/* "ld r26,-X" is undefined */
return (AS2 (adiw,r26,%o1+3) CR_TAB
AS2 (ld,r29,X) CR_TAB
AS2 (ld,r28,-X) CR_TAB
AS2 (ld,__tmp_reg__,-X) CR_TAB
AS2 (sbiw,r26,1) CR_TAB
AS2 (ld,r26,X) CR_TAB
AS2 (mov,r27,__tmp_reg__));
}
*l = 6;
if (reg_dest == REG_X - 2)
return (AS2 (adiw,r26,%o1) CR_TAB
AS2 (ld,r24,X+) CR_TAB
AS2 (ld,r25,X+) CR_TAB
AS2 (ld,__tmp_reg__,X+) CR_TAB
AS2 (ld,r27,X) CR_TAB
AS2 (mov,r26,__tmp_reg__));
return (AS2 (adiw,r26,%o1) CR_TAB
AS2 (ld,%A0,X+) CR_TAB
AS2 (ld,%B0,X+) CR_TAB
AS2 (ld,%C0,X+) CR_TAB
AS2 (ld,%D0,X) CR_TAB
AS2 (sbiw,r26,%o1+3));
}
if (reg_dest == reg_base)
return *l=5, (AS2 (ldd,%D0,%D1) CR_TAB
AS2 (ldd,%C0,%C1) CR_TAB
AS2 (ldd,__tmp_reg__,%B1) CR_TAB
AS2 (ldd,%A0,%A1) CR_TAB
AS2 (mov,%B0,__tmp_reg__));
else if (reg_dest == reg_base - 2)
return *l=5, (AS2 (ldd,%A0,%A1) CR_TAB
AS2 (ldd,%B0,%B1) CR_TAB
AS2 (ldd,__tmp_reg__,%C1) CR_TAB
AS2 (ldd,%D0,%D1) CR_TAB
AS2 (mov,%C0,__tmp_reg__));
return *l=4, (AS2 (ldd,%A0,%A1) CR_TAB
AS2 (ldd,%B0,%B1) CR_TAB
AS2 (ldd,%C0,%C1) CR_TAB
AS2 (ldd,%D0,%D1));
}
else if (GET_CODE (base) == PRE_DEC) /* (--R) */
return *l=4, (AS2 (ld,%D0,%1) CR_TAB
AS2 (ld,%C0,%1) CR_TAB
AS2 (ld,%B0,%1) CR_TAB
AS2 (ld,%A0,%1));
else if (GET_CODE (base) == POST_INC) /* (R++) */
return *l=4, (AS2 (ld,%A0,%1) CR_TAB
AS2 (ld,%B0,%1) CR_TAB
AS2 (ld,%C0,%1) CR_TAB
AS2 (ld,%D0,%1));
else if (CONSTANT_ADDRESS_P (base))
return *l=8, (AS2 (lds,%A0,%m1) CR_TAB
AS2 (lds,%B0,%m1+1) CR_TAB
AS2 (lds,%C0,%m1+2) CR_TAB
AS2 (lds,%D0,%m1+3));
fatal_insn ("unknown move insn:",insn);
return "";
}
const char *
out_movsi_mr_r (rtx insn, rtx op[], int *l)
{
rtx dest = op[0];
rtx src = op[1];
rtx base = XEXP (dest, 0);
int reg_base = true_regnum (base);
int reg_src = true_regnum (src);
int tmp;
if (!l)
l = &tmp;
if (CONSTANT_ADDRESS_P (base))
return *l=8,(AS2 (sts,%m0,%A1) CR_TAB
AS2 (sts,%m0+1,%B1) CR_TAB
AS2 (sts,%m0+2,%C1) CR_TAB
AS2 (sts,%m0+3,%D1));
if (reg_base > 0) /* (r) */
{
if (reg_base == REG_X) /* (R26) */
{
if (reg_src == REG_X)
{
/* "st X+,r26" is undefined */
if (reg_unused_after (insn, base))
return *l=6, (AS2 (mov,__tmp_reg__,r27) CR_TAB
AS2 (st,X,r26) CR_TAB
AS2 (adiw,r26,1) CR_TAB
AS2 (st,X+,__tmp_reg__) CR_TAB
AS2 (st,X+,r28) CR_TAB
AS2 (st,X,r29));
else
return *l=7, (AS2 (mov,__tmp_reg__,r27) CR_TAB
AS2 (st,X,r26) CR_TAB
AS2 (adiw,r26,1) CR_TAB
AS2 (st,X+,__tmp_reg__) CR_TAB
AS2 (st,X+,r28) CR_TAB
AS2 (st,X,r29) CR_TAB
AS2 (sbiw,r26,3));
}
else if (reg_base == reg_src + 2)
{
if (reg_unused_after (insn, base))
return *l=7, (AS2 (mov,__zero_reg__,%C1) CR_TAB
AS2 (mov,__tmp_reg__,%D1) CR_TAB
AS2 (st,%0+,%A1) CR_TAB
AS2 (st,%0+,%B1) CR_TAB
AS2 (st,%0+,__zero_reg__) CR_TAB
AS2 (st,%0,__tmp_reg__) CR_TAB
AS1 (clr,__zero_reg__));
else
return *l=8, (AS2 (mov,__zero_reg__,%C1) CR_TAB
AS2 (mov,__tmp_reg__,%D1) CR_TAB
AS2 (st,%0+,%A1) CR_TAB
AS2 (st,%0+,%B1) CR_TAB
AS2 (st,%0+,__zero_reg__) CR_TAB
AS2 (st,%0,__tmp_reg__) CR_TAB
AS1 (clr,__zero_reg__) CR_TAB
AS2 (sbiw,r26,3));
}
return *l=5, (AS2 (st,%0+,%A1) CR_TAB
AS2 (st,%0+,%B1) CR_TAB
AS2 (st,%0+,%C1) CR_TAB
AS2 (st,%0,%D1) CR_TAB
AS2 (sbiw,r26,3));
}
else
return *l=4, (AS2 (st,%0,%A1) CR_TAB
AS2 (std,%0+1,%B1) CR_TAB
AS2 (std,%0+2,%C1) CR_TAB
AS2 (std,%0+3,%D1));
}
else if (GET_CODE (base) == PLUS) /* (R + i) */
{
int disp = INTVAL (XEXP (base, 1));
reg_base = REGNO (XEXP (base, 0));
if (disp > MAX_LD_OFFSET (GET_MODE (dest)))
{
if (reg_base != REG_Y)
fatal_insn ("incorrect insn:",insn);
if (disp <= 63 + MAX_LD_OFFSET (GET_MODE (dest)))
return *l = 6, (AS2 (adiw,r28,%o0-60) CR_TAB
AS2 (std,Y+60,%A1) CR_TAB
AS2 (std,Y+61,%B1) CR_TAB
AS2 (std,Y+62,%C1) CR_TAB
AS2 (std,Y+63,%D1) CR_TAB
AS2 (sbiw,r28,%o0-60));
return *l = 8, (AS2 (subi,r28,lo8(-%o0)) CR_TAB
AS2 (sbci,r29,hi8(-%o0)) CR_TAB
AS2 (st,Y,%A1) CR_TAB
AS2 (std,Y+1,%B1) CR_TAB
AS2 (std,Y+2,%C1) CR_TAB
AS2 (std,Y+3,%D1) CR_TAB
AS2 (subi,r28,lo8(%o0)) CR_TAB
AS2 (sbci,r29,hi8(%o0)));
}
if (reg_base == REG_X)
{
/* (X + d) = R */
if (reg_src == REG_X)
{
*l = 9;
return (AS2 (mov,__tmp_reg__,r26) CR_TAB
AS2 (mov,__zero_reg__,r27) CR_TAB
AS2 (adiw,r26,%o0) CR_TAB
AS2 (st,X+,__tmp_reg__) CR_TAB
AS2 (st,X+,__zero_reg__) CR_TAB
AS2 (st,X+,r28) CR_TAB
AS2 (st,X,r29) CR_TAB
AS1 (clr,__zero_reg__) CR_TAB
AS2 (sbiw,r26,%o0+3));
}
else if (reg_src == REG_X - 2)
{
*l = 9;
return (AS2 (mov,__tmp_reg__,r26) CR_TAB
AS2 (mov,__zero_reg__,r27) CR_TAB
AS2 (adiw,r26,%o0) CR_TAB
AS2 (st,X+,r24) CR_TAB
AS2 (st,X+,r25) CR_TAB
AS2 (st,X+,__tmp_reg__) CR_TAB
AS2 (st,X,__zero_reg__) CR_TAB
AS1 (clr,__zero_reg__) CR_TAB
AS2 (sbiw,r26,%o0+3));
}
*l = 6;
return (AS2 (adiw,r26,%o0) CR_TAB
AS2 (st,X+,%A1) CR_TAB
AS2 (st,X+,%B1) CR_TAB
AS2 (st,X+,%C1) CR_TAB
AS2 (st,X,%D1) CR_TAB
AS2 (sbiw,r26,%o0+3));
}
return *l=4, (AS2 (std,%A0,%A1) CR_TAB
AS2 (std,%B0,%B1) CR_TAB
AS2 (std,%C0,%C1) CR_TAB
AS2 (std,%D0,%D1));
}
else if (GET_CODE (base) == PRE_DEC) /* (--R) */
return *l=4, (AS2 (st,%0,%D1) CR_TAB
AS2 (st,%0,%C1) CR_TAB
AS2 (st,%0,%B1) CR_TAB
AS2 (st,%0,%A1));
else if (GET_CODE (base) == POST_INC) /* (R++) */
return *l=4, (AS2 (st,%0,%A1) CR_TAB
AS2 (st,%0,%B1) CR_TAB
AS2 (st,%0,%C1) CR_TAB
AS2 (st,%0,%D1));
fatal_insn ("unknown move insn:",insn);
return "";
}
const char *
output_movsisf(rtx insn, rtx operands[], int *l)
{
int dummy;
rtx dest = operands[0];
rtx src = operands[1];
int *real_l = l;
if (!l)
l = &dummy;
if (register_operand (dest, VOIDmode))
{
if (register_operand (src, VOIDmode)) /* mov r,r */
{
if (true_regnum (dest) > true_regnum (src))
{
if (AVR_HAVE_MOVW)
{
*l = 2;
return (AS2 (movw,%C0,%C1) CR_TAB
AS2 (movw,%A0,%A1));
}
*l = 4;
return (AS2 (mov,%D0,%D1) CR_TAB
AS2 (mov,%C0,%C1) CR_TAB
AS2 (mov,%B0,%B1) CR_TAB
AS2 (mov,%A0,%A1));
}
else
{
if (AVR_HAVE_MOVW)
{
*l = 2;
return (AS2 (movw,%A0,%A1) CR_TAB
AS2 (movw,%C0,%C1));
}
*l = 4;
return (AS2 (mov,%A0,%A1) CR_TAB
AS2 (mov,%B0,%B1) CR_TAB
AS2 (mov,%C0,%C1) CR_TAB
AS2 (mov,%D0,%D1));
}
}
else if (CONSTANT_P (src))
{
if (test_hard_reg_class (LD_REGS, dest)) /* ldi d,i */
{
*l = 4;
return (AS2 (ldi,%A0,lo8(%1)) CR_TAB
AS2 (ldi,%B0,hi8(%1)) CR_TAB
AS2 (ldi,%C0,hlo8(%1)) CR_TAB
AS2 (ldi,%D0,hhi8(%1)));
}
if (GET_CODE (src) == CONST_INT)
{
const char *const clr_op0 =
AVR_HAVE_MOVW ? (AS1 (clr,%A0) CR_TAB
AS1 (clr,%B0) CR_TAB
AS2 (movw,%C0,%A0))
: (AS1 (clr,%A0) CR_TAB
AS1 (clr,%B0) CR_TAB
AS1 (clr,%C0) CR_TAB
AS1 (clr,%D0));
if (src == const0_rtx) /* mov r,L */
{
*l = AVR_HAVE_MOVW ? 3 : 4;
return clr_op0;
}
else if (src == const1_rtx)
{
if (!real_l)
output_asm_insn (clr_op0, operands);
*l = AVR_HAVE_MOVW ? 4 : 5;
return AS1 (inc,%A0);
}
else if (src == constm1_rtx)
{
/* Immediate constants -1 to any register */
if (AVR_HAVE_MOVW)
{
*l = 4;
return (AS1 (clr,%A0) CR_TAB
AS1 (dec,%A0) CR_TAB
AS2 (mov,%B0,%A0) CR_TAB
AS2 (movw,%C0,%A0));
}
*l = 5;
return (AS1 (clr,%A0) CR_TAB
AS1 (dec,%A0) CR_TAB
AS2 (mov,%B0,%A0) CR_TAB
AS2 (mov,%C0,%A0) CR_TAB
AS2 (mov,%D0,%A0));
}
else
{
int bit_nr = exact_log2 (INTVAL (src));
if (bit_nr >= 0)
{
*l = AVR_HAVE_MOVW ? 5 : 6;
if (!real_l)
{
output_asm_insn (clr_op0, operands);
output_asm_insn ("set", operands);
}
if (!real_l)
avr_output_bld (operands, bit_nr);
return "";
}
}
}
/* Last resort, better than loading from memory. */
*l = 10;
return (AS2 (mov,__tmp_reg__,r31) CR_TAB
AS2 (ldi,r31,lo8(%1)) CR_TAB
AS2 (mov,%A0,r31) CR_TAB
AS2 (ldi,r31,hi8(%1)) CR_TAB
AS2 (mov,%B0,r31) CR_TAB
AS2 (ldi,r31,hlo8(%1)) CR_TAB
AS2 (mov,%C0,r31) CR_TAB
AS2 (ldi,r31,hhi8(%1)) CR_TAB
AS2 (mov,%D0,r31) CR_TAB
AS2 (mov,r31,__tmp_reg__));
}
else if (GET_CODE (src) == MEM)
return out_movsi_r_mr (insn, operands, real_l); /* mov r,m */
}
else if (GET_CODE (dest) == MEM)
{
const char *templ;
if (src == const0_rtx)
operands[1] = zero_reg_rtx;
templ = out_movsi_mr_r (insn, operands, real_l);
if (!real_l)
output_asm_insn (templ, operands);
operands[1] = src;
return "";
}
fatal_insn ("invalid insn:", insn);
return "";
}
const char *
out_movqi_mr_r (rtx insn, rtx op[], int *l)
{
rtx dest = op[0];
rtx src = op[1];
rtx x = XEXP (dest, 0);
int dummy;
if (!l)
l = &dummy;
if (CONSTANT_ADDRESS_P (x))
{
if (CONST_INT_P (x) && INTVAL (x) == SREG_ADDR)
{
*l = 1;
return AS2 (out,__SREG__,%1);
}
if (optimize > 0 && io_address_operand (x, QImode))
{
*l = 1;
return AS2 (out,%m0-0x20,%1);
}
*l = 2;
return AS2 (sts,%m0,%1);
}
/* memory access by reg+disp */
else if (GET_CODE (x) == PLUS
&& REG_P (XEXP (x,0))
&& GET_CODE (XEXP (x,1)) == CONST_INT)
{
if ((INTVAL (XEXP (x,1)) - GET_MODE_SIZE (GET_MODE (dest))) >= 63)
{
int disp = INTVAL (XEXP (x,1));
if (REGNO (XEXP (x,0)) != REG_Y)
fatal_insn ("incorrect insn:",insn);
if (disp <= 63 + MAX_LD_OFFSET (GET_MODE (dest)))
return *l = 3, (AS2 (adiw,r28,%o0-63) CR_TAB
AS2 (std,Y+63,%1) CR_TAB
AS2 (sbiw,r28,%o0-63));
return *l = 5, (AS2 (subi,r28,lo8(-%o0)) CR_TAB
AS2 (sbci,r29,hi8(-%o0)) CR_TAB
AS2 (st,Y,%1) CR_TAB
AS2 (subi,r28,lo8(%o0)) CR_TAB
AS2 (sbci,r29,hi8(%o0)));
}
else if (REGNO (XEXP (x,0)) == REG_X)
{
if (reg_overlap_mentioned_p (src, XEXP (x, 0)))
{
if (reg_unused_after (insn, XEXP (x,0)))
return *l = 3, (AS2 (mov,__tmp_reg__,%1) CR_TAB
AS2 (adiw,r26,%o0) CR_TAB
AS2 (st,X,__tmp_reg__));
return *l = 4, (AS2 (mov,__tmp_reg__,%1) CR_TAB
AS2 (adiw,r26,%o0) CR_TAB
AS2 (st,X,__tmp_reg__) CR_TAB
AS2 (sbiw,r26,%o0));
}
else
{
if (reg_unused_after (insn, XEXP (x,0)))
return *l = 2, (AS2 (adiw,r26,%o0) CR_TAB
AS2 (st,X,%1));
return *l = 3, (AS2 (adiw,r26,%o0) CR_TAB
AS2 (st,X,%1) CR_TAB
AS2 (sbiw,r26,%o0));
}
}
*l = 1;
return AS2 (std,%0,%1);
}
*l = 1;
return AS2 (st,%0,%1);
}
const char *
out_movhi_mr_r (rtx insn, rtx op[], int *l)
{
rtx dest = op[0];
rtx src = op[1];
rtx base = XEXP (dest, 0);
int reg_base = true_regnum (base);
int reg_src = true_regnum (src);
/* "volatile" forces writing high byte first, even if less efficient,
for correct operation with 16-bit I/O registers. */
int mem_volatile_p = MEM_VOLATILE_P (dest);
int tmp;
if (!l)
l = &tmp;
if (CONSTANT_ADDRESS_P (base))
{
if (optimize > 0 && io_address_operand (base, HImode))
{
*l = 2;
return (AS2 (out,%m0+1-0x20,%B1) CR_TAB
AS2 (out,%m0-0x20,%A1));
}
return *l = 4, (AS2 (sts,%m0+1,%B1) CR_TAB
AS2 (sts,%m0,%A1));
}
if (reg_base > 0)
{
if (reg_base == REG_X)
{
if (reg_src == REG_X)
{
/* "st X+,r26" and "st -X,r26" are undefined. */
if (!mem_volatile_p && reg_unused_after (insn, src))
return *l=4, (AS2 (mov,__tmp_reg__,r27) CR_TAB
AS2 (st,X,r26) CR_TAB
AS2 (adiw,r26,1) CR_TAB
AS2 (st,X,__tmp_reg__));
else
return *l=5, (AS2 (mov,__tmp_reg__,r27) CR_TAB
AS2 (adiw,r26,1) CR_TAB
AS2 (st,X,__tmp_reg__) CR_TAB
AS2 (sbiw,r26,1) CR_TAB
AS2 (st,X,r26));
}
else
{
if (!mem_volatile_p && reg_unused_after (insn, base))
return *l=2, (AS2 (st,X+,%A1) CR_TAB
AS2 (st,X,%B1));
else
return *l=3, (AS2 (adiw,r26,1) CR_TAB
AS2 (st,X,%B1) CR_TAB
AS2 (st,-X,%A1));
}
}
else
return *l=2, (AS2 (std,%0+1,%B1) CR_TAB
AS2 (st,%0,%A1));
}
else if (GET_CODE (base) == PLUS)
{
int disp = INTVAL (XEXP (base, 1));
reg_base = REGNO (XEXP (base, 0));
if (disp > MAX_LD_OFFSET (GET_MODE (dest)))
{
if (reg_base != REG_Y)
fatal_insn ("incorrect insn:",insn);
if (disp <= 63 + MAX_LD_OFFSET (GET_MODE (dest)))
return *l = 4, (AS2 (adiw,r28,%o0-62) CR_TAB
AS2 (std,Y+63,%B1) CR_TAB
AS2 (std,Y+62,%A1) CR_TAB
AS2 (sbiw,r28,%o0-62));
return *l = 6, (AS2 (subi,r28,lo8(-%o0)) CR_TAB
AS2 (sbci,r29,hi8(-%o0)) CR_TAB
AS2 (std,Y+1,%B1) CR_TAB
AS2 (st,Y,%A1) CR_TAB
AS2 (subi,r28,lo8(%o0)) CR_TAB
AS2 (sbci,r29,hi8(%o0)));
}
if (reg_base == REG_X)
{
/* (X + d) = R */
if (reg_src == REG_X)
{
*l = 7;
return (AS2 (mov,__tmp_reg__,r26) CR_TAB
AS2 (mov,__zero_reg__,r27) CR_TAB
AS2 (adiw,r26,%o0+1) CR_TAB
AS2 (st,X,__zero_reg__) CR_TAB
AS2 (st,-X,__tmp_reg__) CR_TAB
AS1 (clr,__zero_reg__) CR_TAB
AS2 (sbiw,r26,%o0));
}
*l = 4;
return (AS2 (adiw,r26,%o0+1) CR_TAB
AS2 (st,X,%B1) CR_TAB
AS2 (st,-X,%A1) CR_TAB
AS2 (sbiw,r26,%o0));
}
return *l=2, (AS2 (std,%B0,%B1) CR_TAB
AS2 (std,%A0,%A1));
}
else if (GET_CODE (base) == PRE_DEC) /* (--R) */
return *l=2, (AS2 (st,%0,%B1) CR_TAB
AS2 (st,%0,%A1));
else if (GET_CODE (base) == POST_INC) /* (R++) */
{
if (mem_volatile_p)
{
if (REGNO (XEXP (base, 0)) == REG_X)
{
*l = 4;
return (AS2 (adiw,r26,1) CR_TAB
AS2 (st,X,%B1) CR_TAB
AS2 (st,-X,%A1) CR_TAB
AS2 (adiw,r26,2));
}
else
{
*l = 3;
return (AS2 (std,%p0+1,%B1) CR_TAB
AS2 (st,%p0,%A1) CR_TAB
AS2 (adiw,%r0,2));
}
}
*l = 2;
return (AS2 (st,%0,%A1) CR_TAB
AS2 (st,%0,%B1));
}
fatal_insn ("unknown move insn:",insn);
return "";
}
/* Return 1 if frame pointer for current function required. */
bool
avr_frame_pointer_required_p (void)
{
return (cfun->calls_alloca
|| crtl->args.info.nregs == 0
|| get_frame_size () > 0);
}
/* Returns the condition of compare insn INSN, or UNKNOWN. */
static RTX_CODE
compare_condition (rtx insn)
{
rtx next = next_real_insn (insn);
RTX_CODE cond = UNKNOWN;
if (next && GET_CODE (next) == JUMP_INSN)
{
rtx pat = PATTERN (next);
rtx src = SET_SRC (pat);
rtx t = XEXP (src, 0);
cond = GET_CODE (t);
}
return cond;
}
/* Returns nonzero if INSN is a tst insn that only tests the sign. */
static int
compare_sign_p (rtx insn)
{
RTX_CODE cond = compare_condition (insn);
return (cond == GE || cond == LT);
}
/* Returns nonzero if the next insn is a JUMP_INSN with a condition
that needs to be swapped (GT, GTU, LE, LEU). */
int
compare_diff_p (rtx insn)
{
RTX_CODE cond = compare_condition (insn);
return (cond == GT || cond == GTU || cond == LE || cond == LEU) ? cond : 0;
}
/* Returns nonzero if INSN is a compare insn with the EQ or NE condition. */
int
compare_eq_p (rtx insn)
{
RTX_CODE cond = compare_condition (insn);
return (cond == EQ || cond == NE);
}
/* Output test instruction for HImode. */
const char *
out_tsthi (rtx insn, rtx op, int *l)
{
if (compare_sign_p (insn))
{
if (l) *l = 1;
return AS1 (tst,%B0);
}
if (reg_unused_after (insn, op)
&& compare_eq_p (insn))
{
/* Faster than sbiw if we can clobber the operand. */
if (l) *l = 1;
return "or %A0,%B0";
}
if (test_hard_reg_class (ADDW_REGS, op))
{
if (l) *l = 1;
return AS2 (sbiw,%0,0);
}
if (l) *l = 2;
return (AS2 (cp,%A0,__zero_reg__) CR_TAB
AS2 (cpc,%B0,__zero_reg__));
}
/* Output test instruction for SImode. */
const char *
out_tstsi (rtx insn, rtx op, int *l)
{
if (compare_sign_p (insn))
{
if (l) *l = 1;
return AS1 (tst,%D0);
}
if (test_hard_reg_class (ADDW_REGS, op))
{
if (l) *l = 3;
return (AS2 (sbiw,%A0,0) CR_TAB
AS2 (cpc,%C0,__zero_reg__) CR_TAB
AS2 (cpc,%D0,__zero_reg__));
}
if (l) *l = 4;
return (AS2 (cp,%A0,__zero_reg__) CR_TAB
AS2 (cpc,%B0,__zero_reg__) CR_TAB
AS2 (cpc,%C0,__zero_reg__) CR_TAB
AS2 (cpc,%D0,__zero_reg__));
}
/* Generate asm equivalent for various shifts.
Shift count is a CONST_INT, MEM or REG.
This only handles cases that are not already
carefully hand-optimized in ?sh??i3_out. */
void
out_shift_with_cnt (const char *templ, rtx insn, rtx operands[],
int *len, int t_len)
{
rtx op[10];
char str[500];
int second_label = 1;
int saved_in_tmp = 0;
int use_zero_reg = 0;
op[0] = operands[0];
op[1] = operands[1];
op[2] = operands[2];
op[3] = operands[3];
str[0] = 0;
if (len)
*len = 1;
if (GET_CODE (operands[2]) == CONST_INT)
{
int scratch = (GET_CODE (PATTERN (insn)) == PARALLEL);
int count = INTVAL (operands[2]);
int max_len = 10; /* If larger than this, always use a loop. */
if (count <= 0)
{
if (len)
*len = 0;
return;
}
if (count < 8 && !scratch)
use_zero_reg = 1;
if (optimize_size)
max_len = t_len + (scratch ? 3 : (use_zero_reg ? 4 : 5));
if (t_len * count <= max_len)
{
/* Output shifts inline with no loop - faster. */
if (len)
*len = t_len * count;
else
{
while (count-- > 0)
output_asm_insn (templ, op);
}
return;
}
if (scratch)
{
if (!len)
strcat (str, AS2 (ldi,%3,%2));
}
else if (use_zero_reg)
{
/* Hack to save one word: use __zero_reg__ as loop counter.
Set one bit, then shift in a loop until it is 0 again. */
op[3] = zero_reg_rtx;
if (len)
*len = 2;
else
strcat (str, ("set" CR_TAB
AS2 (bld,%3,%2-1)));
}
else
{
/* No scratch register available, use one from LD_REGS (saved in
__tmp_reg__) that doesn't overlap with registers to shift. */
op[3] = gen_rtx_REG (QImode,
((true_regnum (operands[0]) - 1) & 15) + 16);
op[4] = tmp_reg_rtx;
saved_in_tmp = 1;
if (len)
*len = 3; /* Includes "mov %3,%4" after the loop. */
else
strcat (str, (AS2 (mov,%4,%3) CR_TAB
AS2 (ldi,%3,%2)));
}
second_label = 0;
}
else if (GET_CODE (operands[2]) == MEM)
{
rtx op_mov[10];
op[3] = op_mov[0] = tmp_reg_rtx;
op_mov[1] = op[2];
if (len)
out_movqi_r_mr (insn, op_mov, len);
else
output_asm_insn (out_movqi_r_mr (insn, op_mov, NULL), op_mov);
}
else if (register_operand (operands[2], QImode))
{
if (reg_unused_after (insn, operands[2])
&& !reg_overlap_mentioned_p (operands[0], operands[2]))
{
op[3] = op[2];
}
else
{
op[3] = tmp_reg_rtx;
if (!len)
strcat (str, (AS2 (mov,%3,%2) CR_TAB));
}
}
else
fatal_insn ("bad shift insn:", insn);
if (second_label)
{
if (len)
++*len;
else
strcat (str, AS1 (rjmp,2f));
}
if (len)
*len += t_len + 2; /* template + dec + brXX */
else
{
strcat (str, "\n1:\t");
strcat (str, templ);
strcat (str, second_label ? "\n2:\t" : "\n\t");
strcat (str, use_zero_reg ? AS1 (lsr,%3) : AS1 (dec,%3));
strcat (str, CR_TAB);
strcat (str, second_label ? AS1 (brpl,1b) : AS1 (brne,1b));
if (saved_in_tmp)
strcat (str, (CR_TAB AS2 (mov,%3,%4)));
output_asm_insn (str, op);
}
}
/* 8bit shift left ((char)x << i) */
const char *
ashlqi3_out (rtx insn, rtx operands[], int *len)
{
if (GET_CODE (operands[2]) == CONST_INT)
{
int k;
if (!len)
len = &k;
switch (INTVAL (operands[2]))
{
default:
if (INTVAL (operands[2]) < 8)
break;
*len = 1;
return AS1 (clr,%0);
case 1:
*len = 1;
return AS1 (lsl,%0);
case 2:
*len = 2;
return (AS1 (lsl,%0) CR_TAB
AS1 (lsl,%0));
case 3:
*len = 3;
return (AS1 (lsl,%0) CR_TAB
AS1 (lsl,%0) CR_TAB
AS1 (lsl,%0));
case 4:
if (test_hard_reg_class (LD_REGS, operands[0]))
{
*len = 2;
return (AS1 (swap,%0) CR_TAB
AS2 (andi,%0,0xf0));
}
*len = 4;
return (AS1 (lsl,%0) CR_TAB
AS1 (lsl,%0) CR_TAB
AS1 (lsl,%0) CR_TAB
AS1 (lsl,%0));
case 5:
if (test_hard_reg_class (LD_REGS, operands[0]))
{
*len = 3;
return (AS1 (swap,%0) CR_TAB
AS1 (lsl,%0) CR_TAB
AS2 (andi,%0,0xe0));
}
*len = 5;
return (AS1 (lsl,%0) CR_TAB
AS1 (lsl,%0) CR_TAB
AS1 (lsl,%0) CR_TAB
AS1 (lsl,%0) CR_TAB
AS1 (lsl,%0));
case 6:
if (test_hard_reg_class (LD_REGS, operands[0]))
{
*len = 4;
return (AS1 (swap,%0) CR_TAB
AS1 (lsl,%0) CR_TAB
AS1 (lsl,%0) CR_TAB
AS2 (andi,%0,0xc0));
}
*len = 6;
return (AS1 (lsl,%0) CR_TAB
AS1 (lsl,%0) CR_TAB
AS1 (lsl,%0) CR_TAB
AS1 (lsl,%0) CR_TAB
AS1 (lsl,%0) CR_TAB
AS1 (lsl,%0));
case 7:
*len = 3;
return (AS1 (ror,%0) CR_TAB
AS1 (clr,%0) CR_TAB
AS1 (ror,%0));
}
}
else if (CONSTANT_P (operands[2]))
fatal_insn ("internal compiler error. Incorrect shift:", insn);
out_shift_with_cnt (AS1 (lsl,%0),
insn, operands, len, 1);
return "";
}
/* 16bit shift left ((short)x << i) */
const char *
ashlhi3_out (rtx insn, rtx operands[], int *len)
{
if (GET_CODE (operands[2]) == CONST_INT)
{
int scratch = (GET_CODE (PATTERN (insn)) == PARALLEL);
int ldi_ok = test_hard_reg_class (LD_REGS, operands[0]);
int k;
int *t = len;
if (!len)
len = &k;
switch (INTVAL (operands[2]))
{
default:
if (INTVAL (operands[2]) < 16)
break;
*len = 2;
return (AS1 (clr,%B0) CR_TAB
AS1 (clr,%A0));
case 4:
if (optimize_size && scratch)
break; /* 5 */
if (ldi_ok)
{
*len = 6;
return (AS1 (swap,%A0) CR_TAB
AS1 (swap,%B0) CR_TAB
AS2 (andi,%B0,0xf0) CR_TAB
AS2 (eor,%B0,%A0) CR_TAB
AS2 (andi,%A0,0xf0) CR_TAB
AS2 (eor,%B0,%A0));
}
if (scratch)
{
*len = 7;
return (AS1 (swap,%A0) CR_TAB
AS1 (swap,%B0) CR_TAB
AS2 (ldi,%3,0xf0) CR_TAB
"and %B0,%3" CR_TAB
AS2 (eor,%B0,%A0) CR_TAB
"and %A0,%3" CR_TAB
AS2 (eor,%B0,%A0));
}
break; /* optimize_size ? 6 : 8 */
case 5:
if (optimize_size)
break; /* scratch ? 5 : 6 */
if (ldi_ok)
{
*len = 8;
return (AS1 (lsl,%A0) CR_TAB
AS1 (rol,%B0) CR_TAB
AS1 (swap,%A0) CR_TAB
AS1 (swap,%B0) CR_TAB
AS2 (andi,%B0,0xf0) CR_TAB
AS2 (eor,%B0,%A0) CR_TAB
AS2 (andi,%A0,0xf0) CR_TAB
AS2 (eor,%B0,%A0));
}
if (scratch)
{
*len = 9;
return (AS1 (lsl,%A0) CR_TAB
AS1 (rol,%B0) CR_TAB
AS1 (swap,%A0) CR_TAB
AS1 (swap,%B0) CR_TAB
AS2 (ldi,%3,0xf0) CR_TAB
"and %B0,%3" CR_TAB
AS2 (eor,%B0,%A0) CR_TAB
"and %A0,%3" CR_TAB
AS2 (eor,%B0,%A0));
}
break; /* 10 */
case 6:
if (optimize_size)
break; /* scratch ? 5 : 6 */
*len = 9;
return (AS1 (clr,__tmp_reg__) CR_TAB
AS1 (lsr,%B0) CR_TAB
AS1 (ror,%A0) CR_TAB
AS1 (ror,__tmp_reg__) CR_TAB
AS1 (lsr,%B0) CR_TAB
AS1 (ror,%A0) CR_TAB
AS1 (ror,__tmp_reg__) CR_TAB
AS2 (mov,%B0,%A0) CR_TAB
AS2 (mov,%A0,__tmp_reg__));
case 7:
*len = 5;
return (AS1 (lsr,%B0) CR_TAB
AS2 (mov,%B0,%A0) CR_TAB
AS1 (clr,%A0) CR_TAB
AS1 (ror,%B0) CR_TAB
AS1 (ror,%A0));
case 8:
return *len = 2, (AS2 (mov,%B0,%A1) CR_TAB
AS1 (clr,%A0));
case 9:
*len = 3;
return (AS2 (mov,%B0,%A0) CR_TAB
AS1 (clr,%A0) CR_TAB
AS1 (lsl,%B0));
case 10:
*len = 4;
return (AS2 (mov,%B0,%A0) CR_TAB
AS1 (clr,%A0) CR_TAB
AS1 (lsl,%B0) CR_TAB
AS1 (lsl,%B0));
case 11:
*len = 5;
return (AS2 (mov,%B0,%A0) CR_TAB
AS1 (clr,%A0) CR_TAB
AS1 (lsl,%B0) CR_TAB
AS1 (lsl,%B0) CR_TAB
AS1 (lsl,%B0));
case 12:
if (ldi_ok)
{
*len = 4;
return (AS2 (mov,%B0,%A0) CR_TAB
AS1 (clr,%A0) CR_TAB
AS1 (swap,%B0) CR_TAB
AS2 (andi,%B0,0xf0));
}
if (scratch)
{
*len = 5;
return (AS2 (mov,%B0,%A0) CR_TAB
AS1 (clr,%A0) CR_TAB
AS1 (swap,%B0) CR_TAB
AS2 (ldi,%3,0xf0) CR_TAB
"and %B0,%3");
}
*len = 6;
return (AS2 (mov,%B0,%A0) CR_TAB
AS1 (clr,%A0) CR_TAB
AS1 (lsl,%B0) CR_TAB
AS1 (lsl,%B0) CR_TAB
AS1 (lsl,%B0) CR_TAB
AS1 (lsl,%B0));
case 13:
if (ldi_ok)
{
*len = 5;
return (AS2 (mov,%B0,%A0) CR_TAB
AS1 (clr,%A0) CR_TAB
AS1 (swap,%B0) CR_TAB
AS1 (lsl,%B0) CR_TAB
AS2 (andi,%B0,0xe0));
}
if (AVR_HAVE_MUL && scratch)
{
*len = 5;
return (AS2 (ldi,%3,0x20) CR_TAB
AS2 (mul,%A0,%3) CR_TAB
AS2 (mov,%B0,r0) CR_TAB
AS1 (clr,%A0) CR_TAB
AS1 (clr,__zero_reg__));
}
if (optimize_size && scratch)
break; /* 5 */
if (scratch)
{
*len = 6;
return (AS2 (mov,%B0,%A0) CR_TAB
AS1 (clr,%A0) CR_TAB
AS1 (swap,%B0) CR_TAB
AS1 (lsl,%B0) CR_TAB
AS2 (ldi,%3,0xe0) CR_TAB
"and %B0,%3");
}
if (AVR_HAVE_MUL)
{
*len = 6;
return ("set" CR_TAB
AS2 (bld,r1,5) CR_TAB
AS2 (mul,%A0,r1) CR_TAB
AS2 (mov,%B0,r0) CR_TAB
AS1 (clr,%A0) CR_TAB
AS1 (clr,__zero_reg__));
}
*len = 7;
return (AS2 (mov,%B0,%A0) CR_TAB
AS1 (clr,%A0) CR_TAB
AS1 (lsl,%B0) CR_TAB
AS1 (lsl,%B0) CR_TAB
AS1 (lsl,%B0) CR_TAB
AS1 (lsl,%B0) CR_TAB
AS1 (lsl,%B0));
case 14:
if (AVR_HAVE_MUL && ldi_ok)
{
*len = 5;
return (AS2 (ldi,%B0,0x40) CR_TAB
AS2 (mul,%A0,%B0) CR_TAB
AS2 (mov,%B0,r0) CR_TAB
AS1 (clr,%A0) CR_TAB
AS1 (clr,__zero_reg__));
}
if (AVR_HAVE_MUL && scratch)
{
*len = 5;
return (AS2 (ldi,%3,0x40) CR_TAB
AS2 (mul,%A0,%3) CR_TAB
AS2 (mov,%B0,r0) CR_TAB
AS1 (clr,%A0) CR_TAB
AS1 (clr,__zero_reg__));
}
if (optimize_size && ldi_ok)
{
*len = 5;
return (AS2 (mov,%B0,%A0) CR_TAB
AS2 (ldi,%A0,6) "\n1:\t"
AS1 (lsl,%B0) CR_TAB
AS1 (dec,%A0) CR_TAB
AS1 (brne,1b));
}
if (optimize_size && scratch)
break; /* 5 */
*len = 6;
return (AS1 (clr,%B0) CR_TAB
AS1 (lsr,%A0) CR_TAB
AS1 (ror,%B0) CR_TAB
AS1 (lsr,%A0) CR_TAB
AS1 (ror,%B0) CR_TAB
AS1 (clr,%A0));
case 15:
*len = 4;
return (AS1 (clr,%B0) CR_TAB
AS1 (lsr,%A0) CR_TAB
AS1 (ror,%B0) CR_TAB
AS1 (clr,%A0));
}
len = t;
}
out_shift_with_cnt ((AS1 (lsl,%A0) CR_TAB
AS1 (rol,%B0)),
insn, operands, len, 2);
return "";
}
/* 32bit shift left ((long)x << i) */
const char *
ashlsi3_out (rtx insn, rtx operands[], int *len)
{
if (GET_CODE (operands[2]) == CONST_INT)
{
int k;
int *t = len;
if (!len)
len = &k;
switch (INTVAL (operands[2]))
{
default:
if (INTVAL (operands[2]) < 32)
break;
if (AVR_HAVE_MOVW)
return *len = 3, (AS1 (clr,%D0) CR_TAB
AS1 (clr,%C0) CR_TAB
AS2 (movw,%A0,%C0));
*len = 4;
return (AS1 (clr,%D0) CR_TAB
AS1 (clr,%C0) CR_TAB
AS1 (clr,%B0) CR_TAB
AS1 (clr,%A0));
case 8:
{
int reg0 = true_regnum (operands[0]);
int reg1 = true_regnum (operands[1]);
*len = 4;
if (reg0 >= reg1)
return (AS2 (mov,%D0,%C1) CR_TAB
AS2 (mov,%C0,%B1) CR_TAB
AS2 (mov,%B0,%A1) CR_TAB
AS1 (clr,%A0));
else
return (AS1 (clr,%A0) CR_TAB
AS2 (mov,%B0,%A1) CR_TAB
AS2 (mov,%C0,%B1) CR_TAB
AS2 (mov,%D0,%C1));
}
case 16:
{
int reg0 = true_regnum (operands[0]);
int reg1 = true_regnum (operands[1]);
if (reg0 + 2 == reg1)
return *len = 2, (AS1 (clr,%B0) CR_TAB
AS1 (clr,%A0));
if (AVR_HAVE_MOVW)
return *len = 3, (AS2 (movw,%C0,%A1) CR_TAB
AS1 (clr,%B0) CR_TAB
AS1 (clr,%A0));
else
return *len = 4, (AS2 (mov,%C0,%A1) CR_TAB
AS2 (mov,%D0,%B1) CR_TAB
AS1 (clr,%B0) CR_TAB
AS1 (clr,%A0));
}
case 24:
*len = 4;
return (AS2 (mov,%D0,%A1) CR_TAB
AS1 (clr,%C0) CR_TAB
AS1 (clr,%B0) CR_TAB
AS1 (clr,%A0));
case 31:
*len = 6;
return (AS1 (clr,%D0) CR_TAB
AS1 (lsr,%A0) CR_TAB
AS1 (ror,%D0) CR_TAB
AS1 (clr,%C0) CR_TAB
AS1 (clr,%B0) CR_TAB
AS1 (clr,%A0));
}
len = t;
}
out_shift_with_cnt ((AS1 (lsl,%A0) CR_TAB
AS1 (rol,%B0) CR_TAB
AS1 (rol,%C0) CR_TAB
AS1 (rol,%D0)),
insn, operands, len, 4);
return "";
}
/* 8bit arithmetic shift right ((signed char)x >> i) */
const char *
ashrqi3_out (rtx insn, rtx operands[], int *len)
{
if (GET_CODE (operands[2]) == CONST_INT)
{
int k;
if (!len)
len = &k;
switch (INTVAL (operands[2]))
{
case 1:
*len = 1;
return AS1 (asr,%0);
case 2:
*len = 2;
return (AS1 (asr,%0) CR_TAB
AS1 (asr,%0));
case 3:
*len = 3;
return (AS1 (asr,%0) CR_TAB
AS1 (asr,%0) CR_TAB
AS1 (asr,%0));
case 4:
*len = 4;
return (AS1 (asr,%0) CR_TAB
AS1 (asr,%0) CR_TAB
AS1 (asr,%0) CR_TAB
AS1 (asr,%0));
case 5:
*len = 5;
return (AS1 (asr,%0) CR_TAB
AS1 (asr,%0) CR_TAB
AS1 (asr,%0) CR_TAB
AS1 (asr,%0) CR_TAB
AS1 (asr,%0));
case 6:
*len = 4;
return (AS2 (bst,%0,6) CR_TAB
AS1 (lsl,%0) CR_TAB
AS2 (sbc,%0,%0) CR_TAB
AS2 (bld,%0,0));
default:
if (INTVAL (operands[2]) < 8)
break;
/* fall through */
case 7:
*len = 2;
return (AS1 (lsl,%0) CR_TAB
AS2 (sbc,%0,%0));
}
}
else if (CONSTANT_P (operands[2]))
fatal_insn ("internal compiler error. Incorrect shift:", insn);
out_shift_with_cnt (AS1 (asr,%0),
insn, operands, len, 1);
return "";
}
/* 16bit arithmetic shift right ((signed short)x >> i) */
const char *
ashrhi3_out (rtx insn, rtx operands[], int *len)
{
if (GET_CODE (operands[2]) == CONST_INT)
{
int scratch = (GET_CODE (PATTERN (insn)) == PARALLEL);
int ldi_ok = test_hard_reg_class (LD_REGS, operands[0]);
int k;
int *t = len;
if (!len)
len = &k;
switch (INTVAL (operands[2]))
{
case 4:
case 5:
/* XXX try to optimize this too? */
break;
case 6:
if (optimize_size)
break; /* scratch ? 5 : 6 */
*len = 8;
return (AS2 (mov,__tmp_reg__,%A0) CR_TAB
AS2 (mov,%A0,%B0) CR_TAB
AS1 (lsl,__tmp_reg__) CR_TAB
AS1 (rol,%A0) CR_TAB
AS2 (sbc,%B0,%B0) CR_TAB
AS1 (lsl,__tmp_reg__) CR_TAB
AS1 (rol,%A0) CR_TAB
AS1 (rol,%B0));
case 7:
*len = 4;
return (AS1 (lsl,%A0) CR_TAB
AS2 (mov,%A0,%B0) CR_TAB
AS1 (rol,%A0) CR_TAB
AS2 (sbc,%B0,%B0));
case 8:
{
int reg0 = true_regnum (operands[0]);
int reg1 = true_regnum (operands[1]);
if (reg0 == reg1)
return *len = 3, (AS2 (mov,%A0,%B0) CR_TAB
AS1 (lsl,%B0) CR_TAB
AS2 (sbc,%B0,%B0));
else
return *len = 4, (AS2 (mov,%A0,%B1) CR_TAB
AS1 (clr,%B0) CR_TAB
AS2 (sbrc,%A0,7) CR_TAB
AS1 (dec,%B0));
}
case 9:
*len = 4;
return (AS2 (mov,%A0,%B0) CR_TAB
AS1 (lsl,%B0) CR_TAB
AS2 (sbc,%B0,%B0) CR_TAB
AS1 (asr,%A0));
case 10:
*len = 5;
return (AS2 (mov,%A0,%B0) CR_TAB
AS1 (lsl,%B0) CR_TAB
AS2 (sbc,%B0,%B0) CR_TAB
AS1 (asr,%A0) CR_TAB
AS1 (asr,%A0));
case 11:
if (AVR_HAVE_MUL && ldi_ok)
{
*len = 5;
return (AS2 (ldi,%A0,0x20) CR_TAB
AS2 (muls,%B0,%A0) CR_TAB
AS2 (mov,%A0,r1) CR_TAB
AS2 (sbc,%B0,%B0) CR_TAB
AS1 (clr,__zero_reg__));
}
if (optimize_size && scratch)
break; /* 5 */
*len = 6;
return (AS2 (mov,%A0,%B0) CR_TAB
AS1 (lsl,%B0) CR_TAB
AS2 (sbc,%B0,%B0) CR_TAB
AS1 (asr,%A0) CR_TAB
AS1 (asr,%A0) CR_TAB
AS1 (asr,%A0));
case 12:
if (AVR_HAVE_MUL && ldi_ok)
{
*len = 5;
return (AS2 (ldi,%A0,0x10) CR_TAB
AS2 (muls,%B0,%A0) CR_TAB
AS2 (mov,%A0,r1) CR_TAB
AS2 (sbc,%B0,%B0) CR_TAB
AS1 (clr,__zero_reg__));
}
if (optimize_size && scratch)
break; /* 5 */
*len = 7;
return (AS2 (mov,%A0,%B0) CR_TAB
AS1 (lsl,%B0) CR_TAB
AS2 (sbc,%B0,%B0) CR_TAB
AS1 (asr,%A0) CR_TAB
AS1 (asr,%A0) CR_TAB
AS1 (asr,%A0) CR_TAB
AS1 (asr,%A0));
case 13:
if (AVR_HAVE_MUL && ldi_ok)
{
*len = 5;
return (AS2 (ldi,%A0,0x08) CR_TAB
AS2 (muls,%B0,%A0) CR_TAB
AS2 (mov,%A0,r1) CR_TAB
AS2 (sbc,%B0,%B0) CR_TAB
AS1 (clr,__zero_reg__));
}
if (optimize_size)
break; /* scratch ? 5 : 7 */
*len = 8;
return (AS2 (mov,%A0,%B0) CR_TAB
AS1 (lsl,%B0) CR_TAB
AS2 (sbc,%B0,%B0) CR_TAB
AS1 (asr,%A0) CR_TAB
AS1 (asr,%A0) CR_TAB
AS1 (asr,%A0) CR_TAB
AS1 (asr,%A0) CR_TAB
AS1 (asr,%A0));
case 14:
*len = 5;
return (AS1 (lsl,%B0) CR_TAB
AS2 (sbc,%A0,%A0) CR_TAB
AS1 (lsl,%B0) CR_TAB
AS2 (mov,%B0,%A0) CR_TAB
AS1 (rol,%A0));
default:
if (INTVAL (operands[2]) < 16)
break;
/* fall through */
case 15:
return *len = 3, (AS1 (lsl,%B0) CR_TAB
AS2 (sbc,%A0,%A0) CR_TAB
AS2 (mov,%B0,%A0));
}
len = t;
}
out_shift_with_cnt ((AS1 (asr,%B0) CR_TAB
AS1 (ror,%A0)),
insn, operands, len, 2);
return "";
}
/* 32bit arithmetic shift right ((signed long)x >> i) */
const char *
ashrsi3_out (rtx insn, rtx operands[], int *len)
{
if (GET_CODE (operands[2]) == CONST_INT)
{
int k;
int *t = len;
if (!len)
len = &k;
switch (INTVAL (operands[2]))
{
case 8:
{
int reg0 = true_regnum (operands[0]);
int reg1 = true_regnum (operands[1]);
*len=6;
if (reg0 <= reg1)
return (AS2 (mov,%A0,%B1) CR_TAB
AS2 (mov,%B0,%C1) CR_TAB
AS2 (mov,%C0,%D1) CR_TAB
AS1 (clr,%D0) CR_TAB
AS2 (sbrc,%C0,7) CR_TAB
AS1 (dec,%D0));
else
return (AS1 (clr,%D0) CR_TAB
AS2 (sbrc,%D1,7) CR_TAB
AS1 (dec,%D0) CR_TAB
AS2 (mov,%C0,%D1) CR_TAB
AS2 (mov,%B0,%C1) CR_TAB
AS2 (mov,%A0,%B1));
}
case 16:
{
int reg0 = true_regnum (operands[0]);
int reg1 = true_regnum (operands[1]);
if (reg0 == reg1 + 2)
return *len = 4, (AS1 (clr,%D0) CR_TAB
AS2 (sbrc,%B0,7) CR_TAB
AS1 (com,%D0) CR_TAB
AS2 (mov,%C0,%D0));
if (AVR_HAVE_MOVW)
return *len = 5, (AS2 (movw,%A0,%C1) CR_TAB
AS1 (clr,%D0) CR_TAB
AS2 (sbrc,%B0,7) CR_TAB
AS1 (com,%D0) CR_TAB
AS2 (mov,%C0,%D0));
else
return *len = 6, (AS2 (mov,%B0,%D1) CR_TAB
AS2 (mov,%A0,%C1) CR_TAB
AS1 (clr,%D0) CR_TAB
AS2 (sbrc,%B0,7) CR_TAB
AS1 (com,%D0) CR_TAB
AS2 (mov,%C0,%D0));
}
case 24:
return *len = 6, (AS2 (mov,%A0,%D1) CR_TAB
AS1 (clr,%D0) CR_TAB
AS2 (sbrc,%A0,7) CR_TAB
AS1 (com,%D0) CR_TAB
AS2 (mov,%B0,%D0) CR_TAB
AS2 (mov,%C0,%D0));
default:
if (INTVAL (operands[2]) < 32)
break;
/* fall through */
case 31:
if (AVR_HAVE_MOVW)
return *len = 4, (AS1 (lsl,%D0) CR_TAB
AS2 (sbc,%A0,%A0) CR_TAB
AS2 (mov,%B0,%A0) CR_TAB
AS2 (movw,%C0,%A0));
else
return *len = 5, (AS1 (lsl,%D0) CR_TAB
AS2 (sbc,%A0,%A0) CR_TAB
AS2 (mov,%B0,%A0) CR_TAB
AS2 (mov,%C0,%A0) CR_TAB
AS2 (mov,%D0,%A0));
}
len = t;
}
out_shift_with_cnt ((AS1 (asr,%D0) CR_TAB
AS1 (ror,%C0) CR_TAB
AS1 (ror,%B0) CR_TAB
AS1 (ror,%A0)),
insn, operands, len, 4);
return "";
}
/* 8bit logic shift right ((unsigned char)x >> i) */
const char *
lshrqi3_out (rtx insn, rtx operands[], int *len)
{
if (GET_CODE (operands[2]) == CONST_INT)
{
int k;
if (!len)
len = &k;
switch (INTVAL (operands[2]))
{
default:
if (INTVAL (operands[2]) < 8)
break;
*len = 1;
return AS1 (clr,%0);
case 1:
*len = 1;
return AS1 (lsr,%0);
case 2:
*len = 2;
return (AS1 (lsr,%0) CR_TAB
AS1 (lsr,%0));
case 3:
*len = 3;
return (AS1 (lsr,%0) CR_TAB
AS1 (lsr,%0) CR_TAB
AS1 (lsr,%0));
case 4:
if (test_hard_reg_class (LD_REGS, operands[0]))
{
*len=2;
return (AS1 (swap,%0) CR_TAB
AS2 (andi,%0,0x0f));
}
*len = 4;
return (AS1 (lsr,%0) CR_TAB
AS1 (lsr,%0) CR_TAB
AS1 (lsr,%0) CR_TAB
AS1 (lsr,%0));
case 5:
if (test_hard_reg_class (LD_REGS, operands[0]))
{
*len = 3;
return (AS1 (swap,%0) CR_TAB
AS1 (lsr,%0) CR_TAB
AS2 (andi,%0,0x7));
}
*len = 5;
return (AS1 (lsr,%0) CR_TAB
AS1 (lsr,%0) CR_TAB
AS1 (lsr,%0) CR_TAB
AS1 (lsr,%0) CR_TAB
AS1 (lsr,%0));
case 6:
if (test_hard_reg_class (LD_REGS, operands[0]))
{
*len = 4;
return (AS1 (swap,%0) CR_TAB
AS1 (lsr,%0) CR_TAB
AS1 (lsr,%0) CR_TAB
AS2 (andi,%0,0x3));
}
*len = 6;
return (AS1 (lsr,%0) CR_TAB
AS1 (lsr,%0) CR_TAB
AS1 (lsr,%0) CR_TAB
AS1 (lsr,%0) CR_TAB
AS1 (lsr,%0) CR_TAB
AS1 (lsr,%0));
case 7:
*len = 3;
return (AS1 (rol,%0) CR_TAB
AS1 (clr,%0) CR_TAB
AS1 (rol,%0));
}
}
else if (CONSTANT_P (operands[2]))
fatal_insn ("internal compiler error. Incorrect shift:", insn);
out_shift_with_cnt (AS1 (lsr,%0),
insn, operands, len, 1);
return "";
}
/* 16bit logic shift right ((unsigned short)x >> i) */
const char *
lshrhi3_out (rtx insn, rtx operands[], int *len)
{
if (GET_CODE (operands[2]) == CONST_INT)
{
int scratch = (GET_CODE (PATTERN (insn)) == PARALLEL);
int ldi_ok = test_hard_reg_class (LD_REGS, operands[0]);
int k;
int *t = len;
if (!len)
len = &k;
switch (INTVAL (operands[2]))
{
default:
if (INTVAL (operands[2]) < 16)
break;
*len = 2;
return (AS1 (clr,%B0) CR_TAB
AS1 (clr,%A0));
case 4:
if (optimize_size && scratch)
break; /* 5 */
if (ldi_ok)
{
*len = 6;
return (AS1 (swap,%B0) CR_TAB
AS1 (swap,%A0) CR_TAB
AS2 (andi,%A0,0x0f) CR_TAB
AS2 (eor,%A0,%B0) CR_TAB
AS2 (andi,%B0,0x0f) CR_TAB
AS2 (eor,%A0,%B0));
}
if (scratch)
{
*len = 7;
return (AS1 (swap,%B0) CR_TAB
AS1 (swap,%A0) CR_TAB
AS2 (ldi,%3,0x0f) CR_TAB
"and %A0,%3" CR_TAB
AS2 (eor,%A0,%B0) CR_TAB
"and %B0,%3" CR_TAB
AS2 (eor,%A0,%B0));
}
break; /* optimize_size ? 6 : 8 */
case 5:
if (optimize_size)
break; /* scratch ? 5 : 6 */
if (ldi_ok)
{
*len = 8;
return (AS1 (lsr,%B0) CR_TAB
AS1 (ror,%A0) CR_TAB
AS1 (swap,%B0) CR_TAB
AS1 (swap,%A0) CR_TAB
AS2 (andi,%A0,0x0f) CR_TAB
AS2 (eor,%A0,%B0) CR_TAB
AS2 (andi,%B0,0x0f) CR_TAB
AS2 (eor,%A0,%B0));
}
if (scratch)
{
*len = 9;
return (AS1 (lsr,%B0) CR_TAB
AS1 (ror,%A0) CR_TAB
AS1 (swap,%B0) CR_TAB
AS1 (swap,%A0) CR_TAB
AS2 (ldi,%3,0x0f) CR_TAB
"and %A0,%3" CR_TAB
AS2 (eor,%A0,%B0) CR_TAB
"and %B0,%3" CR_TAB
AS2 (eor,%A0,%B0));
}
break; /* 10 */
case 6:
if (optimize_size)
break; /* scratch ? 5 : 6 */
*len = 9;
return (AS1 (clr,__tmp_reg__) CR_TAB
AS1 (lsl,%A0) CR_TAB
AS1 (rol,%B0) CR_TAB
AS1 (rol,__tmp_reg__) CR_TAB
AS1 (lsl,%A0) CR_TAB
AS1 (rol,%B0) CR_TAB
AS1 (rol,__tmp_reg__) CR_TAB
AS2 (mov,%A0,%B0) CR_TAB
AS2 (mov,%B0,__tmp_reg__));
case 7:
*len = 5;
return (AS1 (lsl,%A0) CR_TAB
AS2 (mov,%A0,%B0) CR_TAB
AS1 (rol,%A0) CR_TAB
AS2 (sbc,%B0,%B0) CR_TAB
AS1 (neg,%B0));
case 8:
return *len = 2, (AS2 (mov,%A0,%B1) CR_TAB
AS1 (clr,%B0));
case 9:
*len = 3;
return (AS2 (mov,%A0,%B0) CR_TAB
AS1 (clr,%B0) CR_TAB
AS1 (lsr,%A0));
case 10:
*len = 4;
return (AS2 (mov,%A0,%B0) CR_TAB
AS1 (clr,%B0) CR_TAB
AS1 (lsr,%A0) CR_TAB
AS1 (lsr,%A0));
case 11:
*len = 5;
return (AS2 (mov,%A0,%B0) CR_TAB
AS1 (clr,%B0) CR_TAB
AS1 (lsr,%A0) CR_TAB
AS1 (lsr,%A0) CR_TAB
AS1 (lsr,%A0));
case 12:
if (ldi_ok)
{
*len = 4;
return (AS2 (mov,%A0,%B0) CR_TAB
AS1 (clr,%B0) CR_TAB
AS1 (swap,%A0) CR_TAB
AS2 (andi,%A0,0x0f));
}
if (scratch)
{
*len = 5;
return (AS2 (mov,%A0,%B0) CR_TAB
AS1 (clr,%B0) CR_TAB
AS1 (swap,%A0) CR_TAB
AS2 (ldi,%3,0x0f) CR_TAB
"and %A0,%3");
}
*len = 6;
return (AS2 (mov,%A0,%B0) CR_TAB
AS1 (clr,%B0) CR_TAB
AS1 (lsr,%A0) CR_TAB
AS1 (lsr,%A0) CR_TAB
AS1 (lsr,%A0) CR_TAB
AS1 (lsr,%A0));
case 13:
if (ldi_ok)
{
*len = 5;
return (AS2 (mov,%A0,%B0) CR_TAB
AS1 (clr,%B0) CR_TAB
AS1 (swap,%A0) CR_TAB
AS1 (lsr,%A0) CR_TAB
AS2 (andi,%A0,0x07));
}
if (AVR_HAVE_MUL && scratch)
{
*len = 5;
return (AS2 (ldi,%3,0x08) CR_TAB
AS2 (mul,%B0,%3) CR_TAB
AS2 (mov,%A0,r1) CR_TAB
AS1 (clr,%B0) CR_TAB
AS1 (clr,__zero_reg__));
}
if (optimize_size && scratch)
break; /* 5 */
if (scratch)
{
*len = 6;
return (AS2 (mov,%A0,%B0) CR_TAB
AS1 (clr,%B0) CR_TAB
AS1 (swap,%A0) CR_TAB
AS1 (lsr,%A0) CR_TAB
AS2 (ldi,%3,0x07) CR_TAB
"and %A0,%3");
}
if (AVR_HAVE_MUL)
{
*len = 6;
return ("set" CR_TAB
AS2 (bld,r1,3) CR_TAB
AS2 (mul,%B0,r1) CR_TAB
AS2 (mov,%A0,r1) CR_TAB
AS1 (clr,%B0) CR_TAB
AS1 (clr,__zero_reg__));
}
*len = 7;
return (AS2 (mov,%A0,%B0) CR_TAB
AS1 (clr,%B0) CR_TAB
AS1 (lsr,%A0) CR_TAB
AS1 (lsr,%A0) CR_TAB
AS1 (lsr,%A0) CR_TAB
AS1 (lsr,%A0) CR_TAB
AS1 (lsr,%A0));
case 14:
if (AVR_HAVE_MUL && ldi_ok)
{
*len = 5;
return (AS2 (ldi,%A0,0x04) CR_TAB
AS2 (mul,%B0,%A0) CR_TAB
AS2 (mov,%A0,r1) CR_TAB
AS1 (clr,%B0) CR_TAB
AS1 (clr,__zero_reg__));
}
if (AVR_HAVE_MUL && scratch)
{
*len = 5;
return (AS2 (ldi,%3,0x04) CR_TAB
AS2 (mul,%B0,%3) CR_TAB
AS2 (mov,%A0,r1) CR_TAB
AS1 (clr,%B0) CR_TAB
AS1 (clr,__zero_reg__));
}
if (optimize_size && ldi_ok)
{
*len = 5;
return (AS2 (mov,%A0,%B0) CR_TAB
AS2 (ldi,%B0,6) "\n1:\t"
AS1 (lsr,%A0) CR_TAB
AS1 (dec,%B0) CR_TAB
AS1 (brne,1b));
}
if (optimize_size && scratch)
break; /* 5 */
*len = 6;
return (AS1 (clr,%A0) CR_TAB
AS1 (lsl,%B0) CR_TAB
AS1 (rol,%A0) CR_TAB
AS1 (lsl,%B0) CR_TAB
AS1 (rol,%A0) CR_TAB
AS1 (clr,%B0));
case 15:
*len = 4;
return (AS1 (clr,%A0) CR_TAB
AS1 (lsl,%B0) CR_TAB
AS1 (rol,%A0) CR_TAB
AS1 (clr,%B0));
}
len = t;
}
out_shift_with_cnt ((AS1 (lsr,%B0) CR_TAB
AS1 (ror,%A0)),
insn, operands, len, 2);
return "";
}
/* 32bit logic shift right ((unsigned int)x >> i) */
const char *
lshrsi3_out (rtx insn, rtx operands[], int *len)
{
if (GET_CODE (operands[2]) == CONST_INT)
{
int k;
int *t = len;
if (!len)
len = &k;
switch (INTVAL (operands[2]))
{
default:
if (INTVAL (operands[2]) < 32)
break;
if (AVR_HAVE_MOVW)
return *len = 3, (AS1 (clr,%D0) CR_TAB
AS1 (clr,%C0) CR_TAB
AS2 (movw,%A0,%C0));
*len = 4;
return (AS1 (clr,%D0) CR_TAB
AS1 (clr,%C0) CR_TAB
AS1 (clr,%B0) CR_TAB
AS1 (clr,%A0));
case 8:
{
int reg0 = true_regnum (operands[0]);
int reg1 = true_regnum (operands[1]);
*len = 4;
if (reg0 <= reg1)
return (AS2 (mov,%A0,%B1) CR_TAB
AS2 (mov,%B0,%C1) CR_TAB
AS2 (mov,%C0,%D1) CR_TAB
AS1 (clr,%D0));
else
return (AS1 (clr,%D0) CR_TAB
AS2 (mov,%C0,%D1) CR_TAB
AS2 (mov,%B0,%C1) CR_TAB
AS2 (mov,%A0,%B1));
}
case 16:
{
int reg0 = true_regnum (operands[0]);
int reg1 = true_regnum (operands[1]);
if (reg0 == reg1 + 2)
return *len = 2, (AS1 (clr,%C0) CR_TAB
AS1 (clr,%D0));
if (AVR_HAVE_MOVW)
return *len = 3, (AS2 (movw,%A0,%C1) CR_TAB
AS1 (clr,%C0) CR_TAB
AS1 (clr,%D0));
else
return *len = 4, (AS2 (mov,%B0,%D1) CR_TAB
AS2 (mov,%A0,%C1) CR_TAB
AS1 (clr,%C0) CR_TAB
AS1 (clr,%D0));
}
case 24:
return *len = 4, (AS2 (mov,%A0,%D1) CR_TAB
AS1 (clr,%B0) CR_TAB
AS1 (clr,%C0) CR_TAB
AS1 (clr,%D0));
case 31:
*len = 6;
return (AS1 (clr,%A0) CR_TAB
AS2 (sbrc,%D0,7) CR_TAB
AS1 (inc,%A0) CR_TAB
AS1 (clr,%B0) CR_TAB
AS1 (clr,%C0) CR_TAB
AS1 (clr,%D0));
}
len = t;
}
out_shift_with_cnt ((AS1 (lsr,%D0) CR_TAB
AS1 (ror,%C0) CR_TAB
AS1 (ror,%B0) CR_TAB
AS1 (ror,%A0)),
insn, operands, len, 4);
return "";
}
/* Create RTL split patterns for byte sized rotate expressions. This
produces a series of move instructions and considers overlap situations.
Overlapping non-HImode operands need a scratch register. */
bool
avr_rotate_bytes (rtx operands[])
{
int i, j;
enum machine_mode mode = GET_MODE (operands[0]);
bool overlapped = reg_overlap_mentioned_p (operands[0], operands[1]);
bool same_reg = rtx_equal_p (operands[0], operands[1]);
int num = INTVAL (operands[2]);
rtx scratch = operands[3];
/* Work out if byte or word move is needed. Odd byte rotates need QImode.
Word move if no scratch is needed, otherwise use size of scratch. */
enum machine_mode move_mode = QImode;
int move_size, offset, size;
if (num & 0xf)
move_mode = QImode;
else if ((mode == SImode && !same_reg) || !overlapped)
move_mode = HImode;
else
move_mode = GET_MODE (scratch);
/* Force DI rotate to use QI moves since other DI moves are currently split
into QI moves so forward propagation works better. */
if (mode == DImode)
move_mode = QImode;
/* Make scratch smaller if needed. */
if (SCRATCH != GET_CODE (scratch)
&& HImode == GET_MODE (scratch)
&& QImode == move_mode)
scratch = simplify_gen_subreg (move_mode, scratch, HImode, 0);
move_size = GET_MODE_SIZE (move_mode);
/* Number of bytes/words to rotate. */
offset = (num >> 3) / move_size;
/* Number of moves needed. */
size = GET_MODE_SIZE (mode) / move_size;
/* Himode byte swap is special case to avoid a scratch register. */
if (mode == HImode && same_reg)
{
/* HImode byte swap, using xor. This is as quick as using scratch. */
rtx src, dst;
src = simplify_gen_subreg (move_mode, operands[1], mode, 0);
dst = simplify_gen_subreg (move_mode, operands[0], mode, 1);
if (!rtx_equal_p (dst, src))
{
emit_move_insn (dst, gen_rtx_XOR (QImode, dst, src));
emit_move_insn (src, gen_rtx_XOR (QImode, src, dst));
emit_move_insn (dst, gen_rtx_XOR (QImode, dst, src));
}
}
else
{
#define MAX_SIZE 8 /* GET_MODE_SIZE (DImode) / GET_MODE_SIZE (QImode) */
/* Create linked list of moves to determine move order. */
struct {
rtx src, dst;
int links;
} move[MAX_SIZE + 8];
int blocked, moves;
gcc_assert (size <= MAX_SIZE);
/* Generate list of subreg moves. */
for (i = 0; i < size; i++)
{
int from = i;
int to = (from + offset) % size;
move[i].src = simplify_gen_subreg (move_mode, operands[1],
mode, from * move_size);
move[i].dst = simplify_gen_subreg (move_mode, operands[0],
mode, to * move_size);
move[i].links = -1;
}
/* Mark dependence where a dst of one move is the src of another move.
The first move is a conflict as it must wait until second is
performed. We ignore moves to self - we catch this later. */
if (overlapped)
for (i = 0; i < size; i++)
if (reg_overlap_mentioned_p (move[i].dst, operands[1]))
for (j = 0; j < size; j++)
if (j != i && rtx_equal_p (move[j].src, move[i].dst))
{
/* The dst of move i is the src of move j. */
move[i].links = j;
break;
}
blocked = -1;
moves = 0;
/* Go through move list and perform non-conflicting moves. As each
non-overlapping move is made, it may remove other conflicts
so the process is repeated until no conflicts remain. */
do
{
blocked = -1;
moves = 0;
/* Emit move where dst is not also a src or we have used that
src already. */
for (i = 0; i < size; i++)
if (move[i].src != NULL_RTX)
{
if (move[i].links == -1
|| move[move[i].links].src == NULL_RTX)
{
moves++;
/* Ignore NOP moves to self. */
if (!rtx_equal_p (move[i].dst, move[i].src))
emit_move_insn (move[i].dst, move[i].src);
/* Remove conflict from list. */
move[i].src = NULL_RTX;
}
else
blocked = i;
}
/* Check for deadlock. This is when no moves occurred and we have
at least one blocked move. */
if (moves == 0 && blocked != -1)
{
/* Need to use scratch register to break deadlock.
Add move to put dst of blocked move into scratch.
When this move occurs, it will break chain deadlock.
The scratch register is substituted for real move. */
gcc_assert (SCRATCH != GET_CODE (scratch));
move[size].src = move[blocked].dst;
move[size].dst = scratch;
/* Scratch move is never blocked. */
move[size].links = -1;
/* Make sure we have valid link. */
gcc_assert (move[blocked].links != -1);
/* Replace src of blocking move with scratch reg. */
move[move[blocked].links].src = scratch;
/* Make dependent on scratch move occuring. */
move[blocked].links = size;
size=size+1;
}
}
while (blocked != -1);
}
return true;
}
/* Modifies the length assigned to instruction INSN
LEN is the initially computed length of the insn. */
int
adjust_insn_length (rtx insn, int len)
{
rtx patt = PATTERN (insn);
rtx set;
if (GET_CODE (patt) == SET)
{
rtx op[10];
op[1] = SET_SRC (patt);
op[0] = SET_DEST (patt);
if (general_operand (op[1], VOIDmode)
&& general_operand (op[0], VOIDmode))
{
switch (GET_MODE (op[0]))
{
case QImode:
output_movqi (insn, op, &len);
break;
case HImode:
output_movhi (insn, op, &len);
break;
case SImode:
case SFmode:
output_movsisf (insn, op, &len);
break;
default:
break;
}
}
else if (op[0] == cc0_rtx && REG_P (op[1]))
{
switch (GET_MODE (op[1]))
{
case HImode: out_tsthi (insn, op[1], &len); break;
case SImode: out_tstsi (insn, op[1], &len); break;
default: break;
}
}
else if (GET_CODE (op[1]) == AND)
{
if (GET_CODE (XEXP (op[1],1)) == CONST_INT)
{
HOST_WIDE_INT mask = INTVAL (XEXP (op[1],1));
if (GET_MODE (op[1]) == SImode)
len = (((mask & 0xff) != 0xff)
+ ((mask & 0xff00) != 0xff00)
+ ((mask & 0xff0000L) != 0xff0000L)
+ ((mask & 0xff000000L) != 0xff000000L));
else if (GET_MODE (op[1]) == HImode)
len = (((mask & 0xff) != 0xff)
+ ((mask & 0xff00) != 0xff00));
}
}
else if (GET_CODE (op[1]) == IOR)
{
if (GET_CODE (XEXP (op[1],1)) == CONST_INT)
{
HOST_WIDE_INT mask = INTVAL (XEXP (op[1],1));
if (GET_MODE (op[1]) == SImode)
len = (((mask & 0xff) != 0)
+ ((mask & 0xff00) != 0)
+ ((mask & 0xff0000L) != 0)
+ ((mask & 0xff000000L) != 0));
else if (GET_MODE (op[1]) == HImode)
len = (((mask & 0xff) != 0)
+ ((mask & 0xff00) != 0));
}
}
}
set = single_set (insn);
if (set)
{
rtx op[10];
op[1] = SET_SRC (set);
op[0] = SET_DEST (set);
if (GET_CODE (patt) == PARALLEL
&& general_operand (op[1], VOIDmode)
&& general_operand (op[0], VOIDmode))
{
if (XVECLEN (patt, 0) == 2)
op[2] = XVECEXP (patt, 0, 1);
switch (GET_MODE (op[0]))
{
case QImode:
len = 2;
break;
case HImode:
output_reload_inhi (insn, op, &len);
break;
case SImode:
case SFmode:
output_reload_insisf (insn, op, &len);
break;
default:
break;
}
}
else if (GET_CODE (op[1]) == ASHIFT
|| GET_CODE (op[1]) == ASHIFTRT
|| GET_CODE (op[1]) == LSHIFTRT)
{
rtx ops[10];
ops[0] = op[0];
ops[1] = XEXP (op[1],0);
ops[2] = XEXP (op[1],1);
switch (GET_CODE (op[1]))
{
case ASHIFT:
switch (GET_MODE (op[0]))
{
case QImode: ashlqi3_out (insn,ops,&len); break;
case HImode: ashlhi3_out (insn,ops,&len); break;
case SImode: ashlsi3_out (insn,ops,&len); break;
default: break;
}
break;
case ASHIFTRT:
switch (GET_MODE (op[0]))
{
case QImode: ashrqi3_out (insn,ops,&len); break;
case HImode: ashrhi3_out (insn,ops,&len); break;
case SImode: ashrsi3_out (insn,ops,&len); break;
default: break;
}
break;
case LSHIFTRT:
switch (GET_MODE (op[0]))
{
case QImode: lshrqi3_out (insn,ops,&len); break;
case HImode: lshrhi3_out (insn,ops,&len); break;
case SImode: lshrsi3_out (insn,ops,&len); break;
default: break;
}
break;
default:
break;
}
}
}
return len;
}
/* Return nonzero if register REG dead after INSN. */
int
reg_unused_after (rtx insn, rtx reg)
{
return (dead_or_set_p (insn, reg)
|| (REG_P(reg) && _reg_unused_after (insn, reg)));
}
/* Return nonzero if REG is not used after INSN.
We assume REG is a reload reg, and therefore does
not live past labels. It may live past calls or jumps though. */
int
_reg_unused_after (rtx insn, rtx reg)
{
enum rtx_code code;
rtx set;
/* If the reg is set by this instruction, then it is safe for our
case. Disregard the case where this is a store to memory, since
we are checking a register used in the store address. */
set = single_set (insn);
if (set && GET_CODE (SET_DEST (set)) != MEM
&& reg_overlap_mentioned_p (reg, SET_DEST (set)))
return 1;
while ((insn = NEXT_INSN (insn)))
{
rtx set;
code = GET_CODE (insn);
#if 0
/* If this is a label that existed before reload, then the register
if dead here. However, if this is a label added by reorg, then
the register may still be live here. We can't tell the difference,
so we just ignore labels completely. */
if (code == CODE_LABEL)
return 1;
/* else */
#endif
if (!INSN_P (insn))
continue;
if (code == JUMP_INSN)
return 0;
/* If this is a sequence, we must handle them all at once.
We could have for instance a call that sets the target register,
and an insn in a delay slot that uses the register. In this case,
we must return 0. */
else if (code == INSN && GET_CODE (PATTERN (insn)) == SEQUENCE)
{
int i;
int retval = 0;
for (i = 0; i < XVECLEN (PATTERN (insn), 0); i++)
{
rtx this_insn = XVECEXP (PATTERN (insn), 0, i);
rtx set = single_set (this_insn);
if (GET_CODE (this_insn) == CALL_INSN)
code = CALL_INSN;
else if (GET_CODE (this_insn) == JUMP_INSN)
{
if (INSN_ANNULLED_BRANCH_P (this_insn))
return 0;
code = JUMP_INSN;
}
if (set && reg_overlap_mentioned_p (reg, SET_SRC (set)))
return 0;
if (set && reg_overlap_mentioned_p (reg, SET_DEST (set)))
{
if (GET_CODE (SET_DEST (set)) != MEM)
retval = 1;
else
return 0;
}
if (set == 0
&& reg_overlap_mentioned_p (reg, PATTERN (this_insn)))
return 0;
}
if (retval == 1)
return 1;
else if (code == JUMP_INSN)
return 0;
}
if (code == CALL_INSN)
{
rtx tem;
for (tem = CALL_INSN_FUNCTION_USAGE (insn); tem; tem = XEXP (tem, 1))
if (GET_CODE (XEXP (tem, 0)) == USE
&& REG_P (XEXP (XEXP (tem, 0), 0))
&& reg_overlap_mentioned_p (reg, XEXP (XEXP (tem, 0), 0)))
return 0;
if (call_used_regs[REGNO (reg)])
return 1;
}
set = single_set (insn);
if (set && reg_overlap_mentioned_p (reg, SET_SRC (set)))
return 0;
if (set && reg_overlap_mentioned_p (reg, SET_DEST (set)))
return GET_CODE (SET_DEST (set)) != MEM;
if (set == 0 && reg_overlap_mentioned_p (reg, PATTERN (insn)))
return 0;
}
return 1;
}
/* Target hook for assembling integer objects. The AVR version needs
special handling for references to certain labels. */
static bool
avr_assemble_integer (rtx x, unsigned int size, int aligned_p)
{
if (size == POINTER_SIZE / BITS_PER_UNIT && aligned_p
&& text_segment_operand (x, VOIDmode) )
{
fputs ("\t.word\tgs(", asm_out_file);
output_addr_const (asm_out_file, x);
fputs (")\n", asm_out_file);
return true;
}
return default_assemble_integer (x, size, aligned_p);
}
/* Worker function for ASM_DECLARE_FUNCTION_NAME. */
void
avr_asm_declare_function_name (FILE *file, const char *name, tree decl)
{
/* If the function has the 'signal' or 'interrupt' attribute, test to
make sure that the name of the function is "__vector_NN" so as to
catch when the user misspells the interrupt vector name. */
if (cfun->machine->is_interrupt)
{
if (strncmp (name, "__vector", strlen ("__vector")) != 0)
{
warning_at (DECL_SOURCE_LOCATION (decl), 0,
"%qs appears to be a misspelled interrupt handler",
name);
}
}
else if (cfun->machine->is_signal)
{
if (strncmp (name, "__vector", strlen ("__vector")) != 0)
{
warning_at (DECL_SOURCE_LOCATION (decl), 0,
"%qs appears to be a misspelled signal handler",
name);
}
}
ASM_OUTPUT_TYPE_DIRECTIVE (file, name, "function");
ASM_OUTPUT_LABEL (file, name);
}
/* The routine used to output NUL terminated strings. We use a special
version of this for most svr4 targets because doing so makes the
generated assembly code more compact (and thus faster to assemble)
as well as more readable, especially for targets like the i386
(where the only alternative is to output character sequences as
comma separated lists of numbers). */
void
gas_output_limited_string(FILE *file, const char *str)
{
const unsigned char *_limited_str = (const unsigned char *) str;
unsigned ch;
fprintf (file, "%s\"", STRING_ASM_OP);
for (; (ch = *_limited_str); _limited_str++)
{
int escape;
switch (escape = ESCAPES[ch])
{
case 0:
putc (ch, file);
break;
case 1:
fprintf (file, "\\%03o", ch);
break;
default:
putc ('\\', file);
putc (escape, file);
break;
}
}
fprintf (file, "\"\n");
}
/* The routine used to output sequences of byte values. We use a special
version of this for most svr4 targets because doing so makes the
generated assembly code more compact (and thus faster to assemble)
as well as more readable. Note that if we find subparts of the
character sequence which end with NUL (and which are shorter than
STRING_LIMIT) we output those using ASM_OUTPUT_LIMITED_STRING. */
void
gas_output_ascii(FILE *file, const char *str, size_t length)
{
const unsigned char *_ascii_bytes = (const unsigned char *) str;
const unsigned char *limit = _ascii_bytes + length;
unsigned bytes_in_chunk = 0;
for (; _ascii_bytes < limit; _ascii_bytes++)
{
const unsigned char *p;
if (bytes_in_chunk >= 60)
{
fprintf (file, "\"\n");
bytes_in_chunk = 0;
}
for (p = _ascii_bytes; p < limit && *p != '\0'; p++)
continue;
if (p < limit && (p - _ascii_bytes) <= (signed)STRING_LIMIT)
{
if (bytes_in_chunk > 0)
{
fprintf (file, "\"\n");
bytes_in_chunk = 0;
}
gas_output_limited_string (file, (const char*)_ascii_bytes);
_ascii_bytes = p;
}
else
{
int escape;
unsigned ch;
if (bytes_in_chunk == 0)
fprintf (file, "\t.ascii\t\"");
switch (escape = ESCAPES[ch = *_ascii_bytes])
{
case 0:
putc (ch, file);
bytes_in_chunk++;
break;
case 1:
fprintf (file, "\\%03o", ch);
bytes_in_chunk += 4;
break;
default:
putc ('\\', file);
putc (escape, file);
bytes_in_chunk += 2;
break;
}
}
}
if (bytes_in_chunk > 0)
fprintf (file, "\"\n");
}
/* Return value is nonzero if pseudos that have been
assigned to registers of class CLASS would likely be spilled
because registers of CLASS are needed for spill registers. */
static bool
avr_class_likely_spilled_p (reg_class_t c)
{
return (c != ALL_REGS && c != ADDW_REGS);
}
/* Valid attributes:
progmem - put data to program memory;
signal - make a function to be hardware interrupt. After function
prologue interrupts are disabled;
interrupt - make a function to be hardware interrupt. After function
prologue interrupts are enabled;
naked - don't generate function prologue/epilogue and `ret' command.
Only `progmem' attribute valid for type. */
/* Handle a "progmem" attribute; arguments as in
struct attribute_spec.handler. */
static tree
avr_handle_progmem_attribute (tree *node, tree name,
tree args ATTRIBUTE_UNUSED,
int flags ATTRIBUTE_UNUSED,
bool *no_add_attrs)
{
if (DECL_P (*node))
{
if (TREE_CODE (*node) == TYPE_DECL)
{
/* This is really a decl attribute, not a type attribute,
but try to handle it for GCC 3.0 backwards compatibility. */
tree type = TREE_TYPE (*node);
tree attr = tree_cons (name, args, TYPE_ATTRIBUTES (type));
tree newtype = build_type_attribute_variant (type, attr);
TYPE_MAIN_VARIANT (newtype) = TYPE_MAIN_VARIANT (type);
TREE_TYPE (*node) = newtype;
*no_add_attrs = true;
}
else if (TREE_STATIC (*node) || DECL_EXTERNAL (*node))
{
*no_add_attrs = false;
}
else
{
warning (OPT_Wattributes, "%qE attribute ignored",
name);
*no_add_attrs = true;
}
}
return NULL_TREE;
}
/* Handle an attribute requiring a FUNCTION_DECL; arguments as in
struct attribute_spec.handler. */
static tree
avr_handle_fndecl_attribute (tree *node, tree name,
tree args ATTRIBUTE_UNUSED,
int flags ATTRIBUTE_UNUSED,
bool *no_add_attrs)
{
if (TREE_CODE (*node) != FUNCTION_DECL)
{
warning (OPT_Wattributes, "%qE attribute only applies to functions",
name);
*no_add_attrs = true;
}
return NULL_TREE;
}
static tree
avr_handle_fntype_attribute (tree *node, tree name,
tree args ATTRIBUTE_UNUSED,
int flags ATTRIBUTE_UNUSED,
bool *no_add_attrs)
{
if (TREE_CODE (*node) != FUNCTION_TYPE)
{
warning (OPT_Wattributes, "%qE attribute only applies to functions",
name);
*no_add_attrs = true;
}
return NULL_TREE;
}
/* Look for attribute `progmem' in DECL
if found return 1, otherwise 0. */
int
avr_progmem_p (tree decl, tree attributes)
{
tree a;
if (TREE_CODE (decl) != VAR_DECL)
return 0;
if (NULL_TREE
!= lookup_attribute ("progmem", attributes))
return 1;
a=decl;
do
a = TREE_TYPE(a);
while (TREE_CODE (a) == ARRAY_TYPE);
if (a == error_mark_node)
return 0;
if (NULL_TREE != lookup_attribute ("progmem", TYPE_ATTRIBUTES (a)))
return 1;
return 0;
}
/* Add the section attribute if the variable is in progmem. */
static void
avr_insert_attributes (tree node, tree *attributes)
{
if (TREE_CODE (node) == VAR_DECL
&& (TREE_STATIC (node) || DECL_EXTERNAL (node))
&& avr_progmem_p (node, *attributes))
{
tree node0 = node;
/* For C++, we have to peel arrays in order to get correct
determination of readonlyness. */
do
node0 = TREE_TYPE (node0);
while (TREE_CODE (node0) == ARRAY_TYPE);
if (error_mark_node == node0)
return;
if (TYPE_READONLY (node0))
{
static const char dsec[] = ".progmem.data";
*attributes = tree_cons (get_identifier ("section"),
build_tree_list (NULL, build_string (strlen (dsec), dsec)),
*attributes);
}
else
{
error ("variable %q+D must be const in order to be put into"
" read-only section by means of %<__attribute__((progmem))%>",
node);
}
}
}
/* A get_unnamed_section callback for switching to progmem_section. */
static void
avr_output_progmem_section_asm_op (const void *arg ATTRIBUTE_UNUSED)
{
fprintf (asm_out_file,
"\t.section .progmem.gcc_sw_table, \"%s\", @progbits\n",
AVR_HAVE_JMP_CALL ? "a" : "ax");
/* Should already be aligned, this is just to be safe if it isn't. */
fprintf (asm_out_file, "\t.p2align 1\n");
}
/* Implement TARGET_ASM_INIT_SECTIONS. */
static void
avr_asm_init_sections (void)
{
progmem_section = get_unnamed_section (AVR_HAVE_JMP_CALL ? 0 : SECTION_CODE,
avr_output_progmem_section_asm_op,
NULL);
readonly_data_section = data_section;
}
static unsigned int
avr_section_type_flags (tree decl, const char *name, int reloc)
{
unsigned int flags = default_section_type_flags (decl, name, reloc);
if (strncmp (name, ".noinit", 7) == 0)
{
if (decl && TREE_CODE (decl) == VAR_DECL
&& DECL_INITIAL (decl) == NULL_TREE)
flags |= SECTION_BSS; /* @nobits */
else
warning (0, "only uninitialized variables can be placed in the "
".noinit section");
}
if (0 == strncmp (name, ".progmem.data", strlen (".progmem.data")))
flags &= ~SECTION_WRITE;
return flags;
}
/* Implement `TARGET_ENCODE_SECTION_INFO'. */
static void
avr_encode_section_info (tree decl, rtx rtl, int new_decl_p)
{
/* In avr_handle_progmem_attribute, DECL_INITIAL is not yet
readily available, see PR34734. So we postpone the warning
about uninitialized data in program memory section until here. */
if (new_decl_p
&& decl && DECL_P (decl)
&& NULL_TREE == DECL_INITIAL (decl)
&& avr_progmem_p (decl, DECL_ATTRIBUTES (decl)))
{
warning (OPT_Wuninitialized,
"uninitialized variable %q+D put into "
"program memory area", decl);
}
default_encode_section_info (decl, rtl, new_decl_p);
}
/* Outputs some appropriate text to go at the start of an assembler
file. */
static void
avr_file_start (void)
{
if (avr_current_arch->asm_only)
error ("MCU %qs supported for assembler only", avr_mcu_name);
default_file_start ();
fputs ("__SREG__ = 0x3f\n", asm_out_file);
if (!AVR_HAVE_8BIT_SP)
fputs ("__SP_H__ = 0x3e\n", asm_out_file);
fputs ("__SP_L__ = 0x3d\n", asm_out_file);
fputs ("__tmp_reg__ = 0\n"
"__zero_reg__ = 1\n", asm_out_file);
/* FIXME: output these only if there is anything in the .data / .bss
sections - some code size could be saved by not linking in the
initialization code from libgcc if one or both sections are empty. */
fputs ("\t.global __do_copy_data\n", asm_out_file);
fputs ("\t.global __do_clear_bss\n", asm_out_file);
}
/* Outputs to the stdio stream FILE some
appropriate text to go at the end of an assembler file. */
static void
avr_file_end (void)
{
}
/* Choose the order in which to allocate hard registers for
pseudo-registers local to a basic block.
Store the desired register order in the array `reg_alloc_order'.
Element 0 should be the register to allocate first; element 1, the
next register; and so on. */
void
order_regs_for_local_alloc (void)
{
unsigned int i;
static const int order_0[] = {
24,25,
18,19,
20,21,
22,23,
30,31,
26,27,
28,29,
17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,
0,1,
32,33,34,35
};
static const int order_1[] = {
18,19,
20,21,
22,23,
24,25,
30,31,
26,27,
28,29,
17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,
0,1,
32,33,34,35
};
static const int order_2[] = {
25,24,
23,22,
21,20,
19,18,
30,31,
26,27,
28,29,
17,16,
15,14,13,12,11,10,9,8,7,6,5,4,3,2,
1,0,
32,33,34,35
};
const int *order = (TARGET_ORDER_1 ? order_1 :
TARGET_ORDER_2 ? order_2 :
order_0);
for (i=0; i < ARRAY_SIZE (order_0); ++i)
reg_alloc_order[i] = order[i];
}
/* Implement `TARGET_REGISTER_MOVE_COST' */
static int
avr_register_move_cost (enum machine_mode mode ATTRIBUTE_UNUSED,
reg_class_t from, reg_class_t to)
{
return (from == STACK_REG ? 6
: to == STACK_REG ? 12
: 2);
}
/* Implement `TARGET_MEMORY_MOVE_COST' */
static int
avr_memory_move_cost (enum machine_mode mode, reg_class_t rclass ATTRIBUTE_UNUSED,
bool in ATTRIBUTE_UNUSED)
{
return (mode == QImode ? 2
: mode == HImode ? 4
: mode == SImode ? 8
: mode == SFmode ? 8
: 16);
}
/* Mutually recursive subroutine of avr_rtx_cost for calculating the
cost of an RTX operand given its context. X is the rtx of the
operand, MODE is its mode, and OUTER is the rtx_code of this
operand's parent operator. */
static int
avr_operand_rtx_cost (rtx x, enum machine_mode mode, enum rtx_code outer,
bool speed)
{
enum rtx_code code = GET_CODE (x);
int total;
switch (code)
{
case REG:
case SUBREG:
return 0;
case CONST_INT:
case CONST_DOUBLE:
return COSTS_N_INSNS (GET_MODE_SIZE (mode));
default:
break;
}
total = 0;
avr_rtx_costs (x, code, outer, &total, speed);
return total;
}
/* The AVR backend's rtx_cost function. X is rtx expression whose cost
is to be calculated. Return true if the complete cost has been
computed, and false if subexpressions should be scanned. In either
case, *TOTAL contains the cost result. */
static bool
avr_rtx_costs (rtx x, int codearg, int outer_code ATTRIBUTE_UNUSED, int *total,
bool speed)
{
enum rtx_code code = (enum rtx_code) codearg;
enum machine_mode mode = GET_MODE (x);
HOST_WIDE_INT val;
switch (code)
{
case CONST_INT:
case CONST_DOUBLE:
/* Immediate constants are as cheap as registers. */
*total = 0;
return true;
case MEM:
case CONST:
case LABEL_REF:
case SYMBOL_REF:
*total = COSTS_N_INSNS (GET_MODE_SIZE (mode));
return true;
case NEG:
switch (mode)
{
case QImode:
case SFmode:
*total = COSTS_N_INSNS (1);
break;
case HImode:
*total = COSTS_N_INSNS (3);
break;
case SImode:
*total = COSTS_N_INSNS (7);
break;
default:
return false;
}
*total += avr_operand_rtx_cost (XEXP (x, 0), mode, code, speed);
return true;
case ABS:
switch (mode)
{
case QImode:
case SFmode:
*total = COSTS_N_INSNS (1);
break;
default:
return false;
}
*total += avr_operand_rtx_cost (XEXP (x, 0), mode, code, speed);
return true;
case NOT:
*total = COSTS_N_INSNS (GET_MODE_SIZE (mode));
*total += avr_operand_rtx_cost (XEXP (x, 0), mode, code, speed);
return true;
case ZERO_EXTEND:
*total = COSTS_N_INSNS (GET_MODE_SIZE (mode)
- GET_MODE_SIZE (GET_MODE (XEXP (x, 0))));
*total += avr_operand_rtx_cost (XEXP (x, 0), mode, code, speed);
return true;
case SIGN_EXTEND:
*total = COSTS_N_INSNS (GET_MODE_SIZE (mode) + 2
- GET_MODE_SIZE (GET_MODE (XEXP (x, 0))));
*total += avr_operand_rtx_cost (XEXP (x, 0), mode, code, speed);
return true;
case PLUS:
switch (mode)
{
case QImode:
*total = COSTS_N_INSNS (1);
if (GET_CODE (XEXP (x, 1)) != CONST_INT)
*total += avr_operand_rtx_cost (XEXP (x, 1), mode, code, speed);
break;
case HImode:
if (GET_CODE (XEXP (x, 1)) != CONST_INT)
{
*total = COSTS_N_INSNS (2);
*total += avr_operand_rtx_cost (XEXP (x, 1), mode, code, speed);
}
else if (INTVAL (XEXP (x, 1)) >= -63 && INTVAL (XEXP (x, 1)) <= 63)
*total = COSTS_N_INSNS (1);
else
*total = COSTS_N_INSNS (2);
break;
case SImode:
if (GET_CODE (XEXP (x, 1)) != CONST_INT)
{
*total = COSTS_N_INSNS (4);
*total += avr_operand_rtx_cost (XEXP (x, 1), mode, code, speed);
}
else if (INTVAL (XEXP (x, 1)) >= -63 && INTVAL (XEXP (x, 1)) <= 63)
*total = COSTS_N_INSNS (1);
else
*total = COSTS_N_INSNS (4);
break;
default:
return false;
}
*total += avr_operand_rtx_cost (XEXP (x, 0), mode, code, speed);
return true;
case MINUS:
case AND:
case IOR:
*total = COSTS_N_INSNS (GET_MODE_SIZE (mode));
*total += avr_operand_rtx_cost (XEXP (x, 0), mode, code, speed);
if (GET_CODE (XEXP (x, 1)) != CONST_INT)
*total += avr_operand_rtx_cost (XEXP (x, 1), mode, code, speed);
return true;
case XOR:
*total = COSTS_N_INSNS (GET_MODE_SIZE (mode));
*total += avr_operand_rtx_cost (XEXP (x, 0), mode, code, speed);
*total += avr_operand_rtx_cost (XEXP (x, 1), mode, code, speed);
return true;
case MULT:
switch (mode)
{
case QImode:
if (AVR_HAVE_MUL)
*total = COSTS_N_INSNS (!speed ? 3 : 4);
else if (!speed)
*total = COSTS_N_INSNS (AVR_HAVE_JMP_CALL ? 2 : 1);
else
return false;
break;
case HImode:
if (AVR_HAVE_MUL)
*total = COSTS_N_INSNS (!speed ? 7 : 10);
else if (!speed)
*total = COSTS_N_INSNS (AVR_HAVE_JMP_CALL ? 2 : 1);
else
return false;
break;
default:
return false;
}
*total += avr_operand_rtx_cost (XEXP (x, 0), mode, code, speed);
*total += avr_operand_rtx_cost (XEXP (x, 1), mode, code, speed);
return true;
case DIV:
case MOD:
case UDIV:
case UMOD:
if (!speed)
*total = COSTS_N_INSNS (AVR_HAVE_JMP_CALL ? 2 : 1);
else
return false;
*total += avr_operand_rtx_cost (XEXP (x, 0), mode, code, speed);
*total += avr_operand_rtx_cost (XEXP (x, 1), mode, code, speed);
return true;
case ROTATE:
switch (mode)
{
case QImode:
if (CONST_INT_P (XEXP (x, 1)) && INTVAL (XEXP (x, 1)) == 4)
*total = COSTS_N_INSNS (1);
break;
case HImode:
if (CONST_INT_P (XEXP (x, 1)) && INTVAL (XEXP (x, 1)) == 8)
*total = COSTS_N_INSNS (3);
break;
case SImode:
if (CONST_INT_P (XEXP (x, 1)))
switch (INTVAL (XEXP (x, 1)))
{
case 8:
case 24:
*total = COSTS_N_INSNS (5);
break;
case 16:
*total = COSTS_N_INSNS (AVR_HAVE_MOVW ? 4 : 6);
break;
}
break;
default:
return false;
}
*total += avr_operand_rtx_cost (XEXP (x, 0), mode, code, speed);
return true;
case ASHIFT:
switch (mode)
{
case QImode:
if (GET_CODE (XEXP (x, 1)) != CONST_INT)
{
*total = COSTS_N_INSNS (!speed ? 4 : 17);
*total += avr_operand_rtx_cost (XEXP (x, 1), mode, code, speed);
}
else
{
val = INTVAL (XEXP (x, 1));
if (val == 7)
*total = COSTS_N_INSNS (3);
else if (val >= 0 && val <= 7)
*total = COSTS_N_INSNS (val);
else
*total = COSTS_N_INSNS (1);
}
break;
case HImode:
if (GET_CODE (XEXP (x, 1)) != CONST_INT)
{
*total = COSTS_N_INSNS (!speed ? 5 : 41);
*total += avr_operand_rtx_cost (XEXP (x, 1), mode, code, speed);
}
else
switch (INTVAL (XEXP (x, 1)))
{
case 0:
*total = 0;
break;
case 1:
case 8:
*total = COSTS_N_INSNS (2);
break;
case 9:
*total = COSTS_N_INSNS (3);
break;
case 2:
case 3:
case 10:
case 15:
*total = COSTS_N_INSNS (4);
break;
case 7:
case 11:
case 12:
*total = COSTS_N_INSNS (5);
break;
case 4:
*total = COSTS_N_INSNS (!speed ? 5 : 8);
break;
case 6:
*total = COSTS_N_INSNS (!speed ? 5 : 9);
break;
case 5:
*total = COSTS_N_INSNS (!speed ? 5 : 10);
break;
default:
*total = COSTS_N_INSNS (!speed ? 5 : 41);
*total += avr_operand_rtx_cost (XEXP (x, 1), mode, code, speed);
}
break;
case SImode:
if (GET_CODE (XEXP (x, 1)) != CONST_INT)
{
*total = COSTS_N_INSNS (!speed ? 7 : 113);
*total += avr_operand_rtx_cost (XEXP (x, 1), mode, code, speed);
}
else
switch (INTVAL (XEXP (x, 1)))
{
case 0:
*total = 0;
break;
case 24:
*total = COSTS_N_INSNS (3);
break;
case 1:
case 8:
case 16:
*total = COSTS_N_INSNS (4);
break;
case 31:
*total = COSTS_N_INSNS (6);
break;
case 2:
*total = COSTS_N_INSNS (!speed ? 7 : 8);
break;
default:
*total = COSTS_N_INSNS (!speed ? 7 : 113);
*total += avr_operand_rtx_cost (XEXP (x, 1), mode, code, speed);
}
break;
default:
return false;
}
*total += avr_operand_rtx_cost (XEXP (x, 0), mode, code, speed);
return true;
case ASHIFTRT:
switch (mode)
{
case QImode:
if (GET_CODE (XEXP (x, 1)) != CONST_INT)
{
*total = COSTS_N_INSNS (!speed ? 4 : 17);
*total += avr_operand_rtx_cost (XEXP (x, 1), mode, code, speed);
}
else
{
val = INTVAL (XEXP (x, 1));
if (val == 6)
*total = COSTS_N_INSNS (4);
else if (val == 7)
*total = COSTS_N_INSNS (2);
else if (val >= 0 && val <= 7)
*total = COSTS_N_INSNS (val);
else
*total = COSTS_N_INSNS (1);
}
break;
case HImode:
if (GET_CODE (XEXP (x, 1)) != CONST_INT)
{
*total = COSTS_N_INSNS (!speed ? 5 : 41);
*total += avr_operand_rtx_cost (XEXP (x, 1), mode, code, speed);
}
else
switch (INTVAL (XEXP (x, 1)))
{
case 0:
*total = 0;
break;
case 1:
*total = COSTS_N_INSNS (2);
break;
case 15:
*total = COSTS_N_INSNS (3);
break;
case 2:
case 7:
case 8:
case 9:
*total = COSTS_N_INSNS (4);
break;
case 10:
case 14:
*total = COSTS_N_INSNS (5);
break;
case 11:
*total = COSTS_N_INSNS (!speed ? 5 : 6);
break;
case 12:
*total = COSTS_N_INSNS (!speed ? 5 : 7);
break;
case 6:
case 13:
*total = COSTS_N_INSNS (!speed ? 5 : 8);
break;
default:
*total = COSTS_N_INSNS (!speed ? 5 : 41);
*total += avr_operand_rtx_cost (XEXP (x, 1), mode, code, speed);
}
break;
case SImode:
if (GET_CODE (XEXP (x, 1)) != CONST_INT)
{
*total = COSTS_N_INSNS (!speed ? 7 : 113);
*total += avr_operand_rtx_cost (XEXP (x, 1), mode, code, speed);
}
else
switch (INTVAL (XEXP (x, 1)))
{
case 0:
*total = 0;
break;
case 1:
*total = COSTS_N_INSNS (4);
break;
case 8:
case 16:
case 24:
*total = COSTS_N_INSNS (6);
break;
case 2:
*total = COSTS_N_INSNS (!speed ? 7 : 8);
break;
case 31:
*total = COSTS_N_INSNS (AVR_HAVE_MOVW ? 4 : 5);
break;
default:
*total = COSTS_N_INSNS (!speed ? 7 : 113);
*total += avr_operand_rtx_cost (XEXP (x, 1), mode, code, speed);
}
break;
default:
return false;
}
*total += avr_operand_rtx_cost (XEXP (x, 0), mode, code, speed);
return true;
case LSHIFTRT:
switch (mode)
{
case QImode:
if (GET_CODE (XEXP (x, 1)) != CONST_INT)
{
*total = COSTS_N_INSNS (!speed ? 4 : 17);
*total += avr_operand_rtx_cost (XEXP (x, 1), mode, code, speed);
}
else
{
val = INTVAL (XEXP (x, 1));
if (val == 7)
*total = COSTS_N_INSNS (3);
else if (val >= 0 && val <= 7)
*total = COSTS_N_INSNS (val);
else
*total = COSTS_N_INSNS (1);
}
break;
case HImode:
if (GET_CODE (XEXP (x, 1)) != CONST_INT)
{
*total = COSTS_N_INSNS (!speed ? 5 : 41);
*total += avr_operand_rtx_cost (XEXP (x, 1), mode, code, speed);
}
else
switch (INTVAL (XEXP (x, 1)))
{
case 0:
*total = 0;
break;
case 1:
case 8:
*total = COSTS_N_INSNS (2);
break;
case 9:
*total = COSTS_N_INSNS (3);
break;
case 2:
case 10:
case 15:
*total = COSTS_N_INSNS (4);
break;
case 7:
case 11:
*total = COSTS_N_INSNS (5);
break;
case 3:
case 12:
case 13:
case 14:
*total = COSTS_N_INSNS (!speed ? 5 : 6);
break;
case 4:
*total = COSTS_N_INSNS (!speed ? 5 : 7);
break;
case 5:
case 6:
*total = COSTS_N_INSNS (!speed ? 5 : 9);
break;
default:
*total = COSTS_N_INSNS (!speed ? 5 : 41);
*total += avr_operand_rtx_cost (XEXP (x, 1), mode, code, speed);
}
break;
case SImode:
if (GET_CODE (XEXP (x, 1)) != CONST_INT)
{
*total = COSTS_N_INSNS (!speed ? 7 : 113);
*total += avr_operand_rtx_cost (XEXP (x, 1), mode, code, speed);
}
else
switch (INTVAL (XEXP (x, 1)))
{
case 0:
*total = 0;
break;
case 1:
*total = COSTS_N_INSNS (4);
break;
case 2:
*total = COSTS_N_INSNS (!speed ? 7 : 8);
break;
case 8:
case 16:
case 24:
*total = COSTS_N_INSNS (4);
break;
case 31:
*total = COSTS_N_INSNS (6);
break;
default:
*total = COSTS_N_INSNS (!speed ? 7 : 113);
*total += avr_operand_rtx_cost (XEXP (x, 1), mode, code, speed);
}
break;
default:
return false;
}
*total += avr_operand_rtx_cost (XEXP (x, 0), mode, code, speed);
return true;
case COMPARE:
switch (GET_MODE (XEXP (x, 0)))
{
case QImode:
*total = COSTS_N_INSNS (1);
if (GET_CODE (XEXP (x, 1)) != CONST_INT)
*total += avr_operand_rtx_cost (XEXP (x, 1), mode, code, speed);
break;
case HImode:
*total = COSTS_N_INSNS (2);
if (GET_CODE (XEXP (x, 1)) != CONST_INT)
*total += avr_operand_rtx_cost (XEXP (x, 1), mode, code, speed);
else if (INTVAL (XEXP (x, 1)) != 0)
*total += COSTS_N_INSNS (1);
break;
case SImode:
*total = COSTS_N_INSNS (4);
if (GET_CODE (XEXP (x, 1)) != CONST_INT)
*total += avr_operand_rtx_cost (XEXP (x, 1), mode, code, speed);
else if (INTVAL (XEXP (x, 1)) != 0)
*total += COSTS_N_INSNS (3);
break;
default:
return false;
}
*total += avr_operand_rtx_cost (XEXP (x, 0), mode, code, speed);
return true;
default:
break;
}
return false;
}
/* Calculate the cost of a memory address. */
static int
avr_address_cost (rtx x, bool speed ATTRIBUTE_UNUSED)
{
if (GET_CODE (x) == PLUS
&& GET_CODE (XEXP (x,1)) == CONST_INT
&& (REG_P (XEXP (x,0)) || GET_CODE (XEXP (x,0)) == SUBREG)
&& INTVAL (XEXP (x,1)) >= 61)
return 18;
if (CONSTANT_ADDRESS_P (x))
{
if (optimize > 0 && io_address_operand (x, QImode))
return 2;
return 4;
}
return 4;
}
/* Test for extra memory constraint 'Q'.
It's a memory address based on Y or Z pointer with valid displacement. */
int
extra_constraint_Q (rtx x)
{
if (GET_CODE (XEXP (x,0)) == PLUS
&& REG_P (XEXP (XEXP (x,0), 0))
&& GET_CODE (XEXP (XEXP (x,0), 1)) == CONST_INT
&& (INTVAL (XEXP (XEXP (x,0), 1))
<= MAX_LD_OFFSET (GET_MODE (x))))
{
rtx xx = XEXP (XEXP (x,0), 0);
int regno = REGNO (xx);
if (TARGET_ALL_DEBUG)
{
fprintf (stderr, ("extra_constraint:\n"
"reload_completed: %d\n"
"reload_in_progress: %d\n"),
reload_completed, reload_in_progress);
debug_rtx (x);
}
if (regno >= FIRST_PSEUDO_REGISTER)
return 1; /* allocate pseudos */
else if (regno == REG_Z || regno == REG_Y)
return 1; /* strictly check */
else if (xx == frame_pointer_rtx
|| xx == arg_pointer_rtx)
return 1; /* XXX frame & arg pointer checks */
}
return 0;
}
/* Convert condition code CONDITION to the valid AVR condition code. */
RTX_CODE
avr_normalize_condition (RTX_CODE condition)
{
switch (condition)
{
case GT:
return GE;
case GTU:
return GEU;
case LE:
return LT;
case LEU:
return LTU;
default:
gcc_unreachable ();
}
}
/* This function optimizes conditional jumps. */
static void
avr_reorg (void)
{
rtx insn, pattern;
for (insn = get_insns (); insn; insn = NEXT_INSN (insn))
{
if (! (GET_CODE (insn) == INSN
|| GET_CODE (insn) == CALL_INSN
|| GET_CODE (insn) == JUMP_INSN)
|| !single_set (insn))
continue;
pattern = PATTERN (insn);
if (GET_CODE (pattern) == PARALLEL)
pattern = XVECEXP (pattern, 0, 0);
if (GET_CODE (pattern) == SET
&& SET_DEST (pattern) == cc0_rtx
&& compare_diff_p (insn))
{
if (GET_CODE (SET_SRC (pattern)) == COMPARE)
{
/* Now we work under compare insn. */
pattern = SET_SRC (pattern);
if (true_regnum (XEXP (pattern,0)) >= 0
&& true_regnum (XEXP (pattern,1)) >= 0 )
{
rtx x = XEXP (pattern,0);
rtx next = next_real_insn (insn);
rtx pat = PATTERN (next);
rtx src = SET_SRC (pat);
rtx t = XEXP (src,0);
PUT_CODE (t, swap_condition (GET_CODE (t)));
XEXP (pattern,0) = XEXP (pattern,1);
XEXP (pattern,1) = x;
INSN_CODE (next) = -1;
}
else if (true_regnum (XEXP (pattern, 0)) >= 0
&& XEXP (pattern, 1) == const0_rtx)
{
/* This is a tst insn, we can reverse it. */
rtx next = next_real_insn (insn);
rtx pat = PATTERN (next);
rtx src = SET_SRC (pat);
rtx t = XEXP (src,0);
PUT_CODE (t, swap_condition (GET_CODE (t)));
XEXP (pattern, 1) = XEXP (pattern, 0);
XEXP (pattern, 0) = const0_rtx;
INSN_CODE (next) = -1;
INSN_CODE (insn) = -1;
}
else if (true_regnum (XEXP (pattern,0)) >= 0
&& GET_CODE (XEXP (pattern,1)) == CONST_INT)
{
rtx x = XEXP (pattern,1);
rtx next = next_real_insn (insn);
rtx pat = PATTERN (next);
rtx src = SET_SRC (pat);
rtx t = XEXP (src,0);
enum machine_mode mode = GET_MODE (XEXP (pattern, 0));
if (avr_simplify_comparison_p (mode, GET_CODE (t), x))
{
XEXP (pattern, 1) = gen_int_mode (INTVAL (x) + 1, mode);
PUT_CODE (t, avr_normalize_condition (GET_CODE (t)));
INSN_CODE (next) = -1;
INSN_CODE (insn) = -1;
}
}
}
}
}
}
/* Returns register number for function return value.*/
int
avr_ret_register (void)
{
return 24;
}
/* Create an RTX representing the place where a
library function returns a value of mode MODE. */
rtx
avr_libcall_value (enum machine_mode mode)
{
int offs = GET_MODE_SIZE (mode);
if (offs < 2)
offs = 2;
return gen_rtx_REG (mode, RET_REGISTER + 2 - offs);
}
/* Create an RTX representing the place where a
function returns a value of data type VALTYPE. */
rtx
avr_function_value (const_tree type,
const_tree func ATTRIBUTE_UNUSED,
bool outgoing ATTRIBUTE_UNUSED)
{
unsigned int offs;
if (TYPE_MODE (type) != BLKmode)
return avr_libcall_value (TYPE_MODE (type));
offs = int_size_in_bytes (type);
if (offs < 2)
offs = 2;
if (offs > 2 && offs < GET_MODE_SIZE (SImode))
offs = GET_MODE_SIZE (SImode);
else if (offs > GET_MODE_SIZE (SImode) && offs < GET_MODE_SIZE (DImode))
offs = GET_MODE_SIZE (DImode);
return gen_rtx_REG (BLKmode, RET_REGISTER + 2 - offs);
}
int
test_hard_reg_class (enum reg_class rclass, rtx x)
{
int regno = true_regnum (x);
if (regno < 0)
return 0;
if (TEST_HARD_REG_CLASS (rclass, regno))
return 1;
return 0;
}
int
jump_over_one_insn_p (rtx insn, rtx dest)
{
int uid = INSN_UID (GET_CODE (dest) == LABEL_REF
? XEXP (dest, 0)
: dest);
int jump_addr = INSN_ADDRESSES (INSN_UID (insn));
int dest_addr = INSN_ADDRESSES (uid);
return dest_addr - jump_addr == get_attr_length (insn) + 1;
}
/* Returns 1 if a value of mode MODE can be stored starting with hard
register number REGNO. On the enhanced core, anything larger than
1 byte must start in even numbered register for "movw" to work
(this way we don't have to check for odd registers everywhere). */
int
avr_hard_regno_mode_ok (int regno, enum machine_mode mode)
{
/* NOTE: 8-bit values must not be disallowed for R28 or R29.
Disallowing QI et al. in these regs might lead to code like
(set (subreg:QI (reg:HI 28) n) ...)
which will result in wrong code because reload does not
handle SUBREGs of hard regsisters like this.
This could be fixed in reload. However, it appears
that fixing reload is not wanted by reload people. */
/* Any GENERAL_REGS register can hold 8-bit values. */
if (GET_MODE_SIZE (mode) == 1)
return 1;
/* FIXME: Ideally, the following test is not needed.
However, it turned out that it can reduce the number
of spill fails. AVR and it's poor endowment with
address registers is extreme stress test for reload. */
if (GET_MODE_SIZE (mode) >= 4
&& regno >= REG_X)
return 0;
/* All modes larger than 8 bits should start in an even register. */
return !(regno & 1);
}
const char *
output_reload_inhi (rtx insn ATTRIBUTE_UNUSED, rtx *operands, int *len)
{
int tmp;
if (!len)
len = &tmp;
if (GET_CODE (operands[1]) == CONST_INT)
{
int val = INTVAL (operands[1]);
if ((val & 0xff) == 0)
{
*len = 3;
return (AS2 (mov,%A0,__zero_reg__) CR_TAB
AS2 (ldi,%2,hi8(%1)) CR_TAB
AS2 (mov,%B0,%2));
}
else if ((val & 0xff00) == 0)
{
*len = 3;
return (AS2 (ldi,%2,lo8(%1)) CR_TAB
AS2 (mov,%A0,%2) CR_TAB
AS2 (mov,%B0,__zero_reg__));
}
else if ((val & 0xff) == ((val & 0xff00) >> 8))
{
*len = 3;
return (AS2 (ldi,%2,lo8(%1)) CR_TAB
AS2 (mov,%A0,%2) CR_TAB
AS2 (mov,%B0,%2));
}
}
*len = 4;
return (AS2 (ldi,%2,lo8(%1)) CR_TAB
AS2 (mov,%A0,%2) CR_TAB
AS2 (ldi,%2,hi8(%1)) CR_TAB
AS2 (mov,%B0,%2));
}
const char *
output_reload_insisf (rtx insn ATTRIBUTE_UNUSED, rtx *operands, int *len)
{
rtx src = operands[1];
int cnst = (GET_CODE (src) == CONST_INT);
if (len)
{
if (cnst)
*len = 4 + ((INTVAL (src) & 0xff) != 0)
+ ((INTVAL (src) & 0xff00) != 0)
+ ((INTVAL (src) & 0xff0000) != 0)
+ ((INTVAL (src) & 0xff000000) != 0);
else
*len = 8;
return "";
}
if (cnst && ((INTVAL (src) & 0xff) == 0))
output_asm_insn (AS2 (mov, %A0, __zero_reg__), operands);
else
{
output_asm_insn (AS2 (ldi, %2, lo8(%1)), operands);
output_asm_insn (AS2 (mov, %A0, %2), operands);
}
if (cnst && ((INTVAL (src) & 0xff00) == 0))
output_asm_insn (AS2 (mov, %B0, __zero_reg__), operands);
else
{
output_asm_insn (AS2 (ldi, %2, hi8(%1)), operands);
output_asm_insn (AS2 (mov, %B0, %2), operands);
}
if (cnst && ((INTVAL (src) & 0xff0000) == 0))
output_asm_insn (AS2 (mov, %C0, __zero_reg__), operands);
else
{
output_asm_insn (AS2 (ldi, %2, hlo8(%1)), operands);
output_asm_insn (AS2 (mov, %C0, %2), operands);
}
if (cnst && ((INTVAL (src) & 0xff000000) == 0))
output_asm_insn (AS2 (mov, %D0, __zero_reg__), operands);
else
{
output_asm_insn (AS2 (ldi, %2, hhi8(%1)), operands);
output_asm_insn (AS2 (mov, %D0, %2), operands);
}
return "";
}
void
avr_output_bld (rtx operands[], int bit_nr)
{
static char s[] = "bld %A0,0";
s[5] = 'A' + (bit_nr >> 3);
s[8] = '0' + (bit_nr & 7);
output_asm_insn (s, operands);
}
void
avr_output_addr_vec_elt (FILE *stream, int value)
{
switch_to_section (progmem_section);
if (AVR_HAVE_JMP_CALL)
fprintf (stream, "\t.word gs(.L%d)\n", value);
else
fprintf (stream, "\trjmp .L%d\n", value);
}
/* Returns true if SCRATCH are safe to be allocated as a scratch
registers (for a define_peephole2) in the current function. */
bool
avr_hard_regno_scratch_ok (unsigned int regno)
{
/* Interrupt functions can only use registers that have already been saved
by the prologue, even if they would normally be call-clobbered. */
if ((cfun->machine->is_interrupt || cfun->machine->is_signal)
&& !df_regs_ever_live_p (regno))
return false;
/* Don't allow hard registers that might be part of the frame pointer.
Some places in the compiler just test for [HARD_]FRAME_POINTER_REGNUM
and don't care for a frame pointer that spans more than one register. */
if ((!reload_completed || frame_pointer_needed)
&& (regno == REG_Y || regno == REG_Y + 1))
{
return false;
}
return true;
}
/* Return nonzero if register OLD_REG can be renamed to register NEW_REG. */
int
avr_hard_regno_rename_ok (unsigned int old_reg,
unsigned int new_reg)
{
/* Interrupt functions can only use registers that have already been
saved by the prologue, even if they would normally be
call-clobbered. */
if ((cfun->machine->is_interrupt || cfun->machine->is_signal)
&& !df_regs_ever_live_p (new_reg))
return 0;
/* Don't allow hard registers that might be part of the frame pointer.
Some places in the compiler just test for [HARD_]FRAME_POINTER_REGNUM
and don't care for a frame pointer that spans more than one register. */
if ((!reload_completed || frame_pointer_needed)
&& (old_reg == REG_Y || old_reg == REG_Y + 1
|| new_reg == REG_Y || new_reg == REG_Y + 1))
{
return 0;
}
return 1;
}
/* Output a branch that tests a single bit of a register (QI, HI, SI or DImode)
or memory location in the I/O space (QImode only).
Operand 0: comparison operator (must be EQ or NE, compare bit to zero).
Operand 1: register operand to test, or CONST_INT memory address.
Operand 2: bit number.
Operand 3: label to jump to if the test is true. */
const char *
avr_out_sbxx_branch (rtx insn, rtx operands[])
{
enum rtx_code comp = GET_CODE (operands[0]);
int long_jump = (get_attr_length (insn) >= 4);
int reverse = long_jump || jump_over_one_insn_p (insn, operands[3]);
if (comp == GE)
comp = EQ;
else if (comp == LT)
comp = NE;
if (reverse)
comp = reverse_condition (comp);
if (GET_CODE (operands[1]) == CONST_INT)
{
if (INTVAL (operands[1]) < 0x40)
{
if (comp == EQ)
output_asm_insn (AS2 (sbis,%m1-0x20,%2), operands);
else
output_asm_insn (AS2 (sbic,%m1-0x20,%2), operands);
}
else
{
output_asm_insn (AS2 (in,__tmp_reg__,%m1-0x20), operands);
if (comp == EQ)
output_asm_insn (AS2 (sbrs,__tmp_reg__,%2), operands);
else
output_asm_insn (AS2 (sbrc,__tmp_reg__,%2), operands);
}
}
else /* GET_CODE (operands[1]) == REG */
{
if (GET_MODE (operands[1]) == QImode)
{
if (comp == EQ)
output_asm_insn (AS2 (sbrs,%1,%2), operands);
else
output_asm_insn (AS2 (sbrc,%1,%2), operands);
}
else /* HImode or SImode */
{
static char buf[] = "sbrc %A1,0";
int bit_nr = INTVAL (operands[2]);
buf[3] = (comp == EQ) ? 's' : 'c';
buf[6] = 'A' + (bit_nr >> 3);
buf[9] = '0' + (bit_nr & 7);
output_asm_insn (buf, operands);
}
}
if (long_jump)
return (AS1 (rjmp,.+4) CR_TAB
AS1 (jmp,%x3));
if (!reverse)
return AS1 (rjmp,%x3);
return "";
}
/* Worker function for TARGET_ASM_CONSTRUCTOR. */
static void
avr_asm_out_ctor (rtx symbol, int priority)
{
fputs ("\t.global __do_global_ctors\n", asm_out_file);
default_ctor_section_asm_out_constructor (symbol, priority);
}
/* Worker function for TARGET_ASM_DESTRUCTOR. */
static void
avr_asm_out_dtor (rtx symbol, int priority)
{
fputs ("\t.global __do_global_dtors\n", asm_out_file);
default_dtor_section_asm_out_destructor (symbol, priority);
}
/* Worker function for TARGET_RETURN_IN_MEMORY. */
static bool
avr_return_in_memory (const_tree type, const_tree fntype ATTRIBUTE_UNUSED)
{
if (TYPE_MODE (type) == BLKmode)
{
HOST_WIDE_INT size = int_size_in_bytes (type);
return (size == -1 || size > 8);
}
else
return false;
}
/* Worker function for CASE_VALUES_THRESHOLD. */
unsigned int avr_case_values_threshold (void)
{
return (!AVR_HAVE_JMP_CALL || TARGET_CALL_PROLOGUES) ? 8 : 17;
}
#include "gt-avr.h"