/* Subroutines for insn-output.c for ATMEL AVR micro controllers
Copyright (C) 1998, 1999, 2000, 2001, 2002, 2004, 2005, 2006, 2007, 2008,
- 2009 Free Software Foundation, Inc.
+ 2009, 2010, 2011 Free Software Foundation, Inc.
Contributed by Denis Chertykov (chertykov@gmail.com)
This file is part of GCC.
#include "rtl.h"
#include "regs.h"
#include "hard-reg-set.h"
-#include "real.h"
#include "insn-config.h"
#include "conditions.h"
#include "insn-attr.h"
+#include "insn-codes.h"
#include "flags.h"
#include "reload.h"
#include "tree.h"
#include "output.h"
#include "expr.h"
-#include "toplev.h"
+#include "diagnostic-core.h"
#include "obstack.h"
#include "function.h"
#include "recog.h"
+#include "optabs.h"
#include "ggc.h"
+#include "langhooks.h"
#include "tm_p.h"
#include "target.h"
#include "target-def.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 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, tree);
+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 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 rtx avr_libcall_value (enum machine_mode, const_rtx);
+static bool avr_function_value_regno_p (const unsigned int);
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_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 void avr_init_builtins (void);
+static rtx avr_expand_builtin (tree, rtx, rtx, enum machine_mode, int);
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_t , enum machine_mode,
+ const_tree, bool);
+static void avr_function_arg_advance (cumulative_args_t, enum machine_mode,
+ const_tree, bool);
+static bool avr_function_ok_for_sibcall (tree, tree);
+static void avr_asm_named_section (const char *name, unsigned int flags, tree decl);
+static void avr_encode_section_info (tree, rtx, int);
/* Allocate registers from r25 to r8 for parameters for function calls. */
#define FIRST_CUM_REG 26
static const char *const avr_regnames[] = REGISTER_NAMES;
/* Preprocessor macros to define depending on MCU type. */
-static const char *avr_extra_arch_macro;
+const char *avr_extra_arch_macro;
/* Current architecture. */
const struct base_arch_s *avr_current_arch;
section *progmem_section;
+/* To track if code will use .bss and/or .data. */
+bool avr_need_clear_bss_p = false;
+bool avr_need_copy_data_p = false;
+
/* 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 }
+ /* { name, min_len, max_len, decl_req, type_req, fn_type_req, handler,
+ affects_type_identity } */
+ { "progmem", 0, 0, false, false, false, avr_handle_progmem_attribute,
+ false },
+ { "signal", 0, 0, true, false, false, avr_handle_fndecl_attribute,
+ false },
+ { "interrupt", 0, 0, true, false, false, avr_handle_fndecl_attribute,
+ false },
+ { "naked", 0, 0, false, true, true, avr_handle_fntype_attribute,
+ false },
+ { "OS_task", 0, 0, false, true, true, avr_handle_fntype_attribute,
+ false },
+ { "OS_main", 0, 0, false, true, true, avr_handle_fntype_attribute,
+ false },
+ { NULL, 0, 0, false, false, false, NULL, false }
};
\f
/* Initialize the GCC target structure. */
#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
#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_LIBCALL_VALUE
+#define TARGET_LIBCALL_VALUE avr_libcall_value
+#undef TARGET_FUNCTION_VALUE_REGNO_P
+#define TARGET_FUNCTION_VALUE_REGNO_P avr_function_value_regno_p
+
#undef TARGET_ATTRIBUTE_TABLE
#define TARGET_ATTRIBUTE_TABLE avr_attribute_table
#undef TARGET_ASM_FUNCTION_RODATA_SECTION
#define TARGET_INSERT_ATTRIBUTES avr_insert_attributes
#undef TARGET_SECTION_TYPE_FLAGS
#define TARGET_SECTION_TYPE_FLAGS avr_section_type_flags
+
+/* `TARGET_ASM_NAMED_SECTION' must be defined in avr.h. */
+
+#undef TARGET_ASM_INIT_SECTIONS
+#define TARGET_ASM_INIT_SECTIONS avr_asm_init_sections
+#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_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_CANNOT_MODIFY_JUMPS_P
+#define TARGET_CANNOT_MODIFY_JUMPS_P avr_cannot_modify_jumps_p
+
+#undef TARGET_FUNCTION_OK_FOR_SIBCALL
+#define TARGET_FUNCTION_OK_FOR_SIBCALL avr_function_ok_for_sibcall
+
+#undef TARGET_INIT_BUILTINS
+#define TARGET_INIT_BUILTINS avr_init_builtins
+
+#undef TARGET_EXPAND_BUILTIN
+#define TARGET_EXPAND_BUILTIN avr_expand_builtin
+
+
struct gcc_target targetm = TARGET_INITIALIZER;
\f
-void
-avr_override_options (void)
+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)
- {
- fprintf (stderr, "unknown MCU '%s' specified\nKnown MCU names:\n",
- avr_mcu_name);
- for (t = avr_mcu_types; t->name; t++)
- fprintf (stderr," %s\n", t->name);
- }
-
- avr_current_device = t;
+ avr_current_device = &avr_mcu_types[avr_mcu_index];
avr_current_arch = &avr_arch_types[avr_current_device->arch];
avr_extra_arch_macro = avr_current_device->macro;
init_machine_status = avr_init_machine_status;
}
-/* Worker function for TARGET_CPU_CPP_BUILTINS. */
-
-void
-avr_cpu_cpp_builtins (struct cpp_reader *pfile)
-{
- builtin_define_std ("AVR");
-
- if (avr_current_arch->macro)
- cpp_define (pfile, avr_current_arch->macro);
- if (avr_extra_arch_macro)
- cpp_define (pfile, avr_extra_arch_macro);
- if (avr_current_arch->have_elpm)
- cpp_define (pfile, "__AVR_HAVE_RAMPZ__");
- if (avr_current_arch->have_elpm)
- cpp_define (pfile, "__AVR_HAVE_ELPM__");
- if (avr_current_arch->have_elpmx)
- cpp_define (pfile, "__AVR_HAVE_ELPMX__");
- if (avr_current_arch->have_movw_lpmx)
- {
- cpp_define (pfile, "__AVR_HAVE_MOVW__");
- cpp_define (pfile, "__AVR_HAVE_LPMX__");
- }
- if (avr_current_arch->asm_only)
- cpp_define (pfile, "__AVR_ASM_ONLY__");
- if (avr_current_arch->have_mul)
- {
- cpp_define (pfile, "__AVR_ENHANCED__");
- cpp_define (pfile, "__AVR_HAVE_MUL__");
- }
- if (avr_current_arch->have_jmp_call)
- {
- cpp_define (pfile, "__AVR_MEGA__");
- cpp_define (pfile, "__AVR_HAVE_JMP_CALL__");
- }
- if (avr_current_arch->have_eijmp_eicall)
- {
- cpp_define (pfile, "__AVR_HAVE_EIJMP_EICALL__");
- cpp_define (pfile, "__AVR_3_BYTE_PC__");
- }
- else
- {
- cpp_define (pfile, "__AVR_2_BYTE_PC__");
- }
-
- if (avr_current_device->short_sp)
- cpp_define (pfile, "__AVR_HAVE_8BIT_SP__");
- else
- cpp_define (pfile, "__AVR_HAVE_16BIT_SP__");
-
- if (TARGET_NO_INTERRUPTS)
- cpp_define (pfile, "__NO_INTERRUPTS__");
-}
-
/* return register class from register number. */
static const enum reg_class reg_class_tab[]={
static struct machine_function *
avr_init_machine_status (void)
{
- return ((struct machine_function *)
- ggc_alloc_cleared (sizeof (struct machine_function)));
+ return ggc_alloc_cleared_machine_function ();
}
/* Return register class for register R. */
return ALL_REGS;
}
+/* A helper for the subsequent function attribute used to dig for
+ attribute 'name' in a FUNCTION_DECL or FUNCTION_TYPE */
+
+static inline int
+avr_lookup_function_attribute1 (const_tree func, const char *name)
+{
+ if (FUNCTION_DECL == TREE_CODE (func))
+ {
+ if (NULL_TREE != lookup_attribute (name, DECL_ATTRIBUTES (func)))
+ {
+ return true;
+ }
+
+ func = TREE_TYPE (func);
+ }
+
+ gcc_assert (TREE_CODE (func) == FUNCTION_TYPE
+ || TREE_CODE (func) == METHOD_TYPE);
+
+ return NULL_TREE != lookup_attribute (name, TYPE_ATTRIBUTES (func));
+}
+
/* 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 avr_lookup_function_attribute1 (func, "naked");
}
/* Return nonzero if FUNC is an interrupt function as specified
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 avr_lookup_function_attribute1 (func, "interrupt");
}
/* Return nonzero if FUNC is a signal function as specified
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 avr_lookup_function_attribute1 (func, "signal");
}
/* 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 avr_lookup_function_attribute1 (func, "OS_task");
}
/* 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 avr_lookup_function_attribute1 (func, "OS_main");
}
/* Return the number of hard registers to push/pop in the prologue/epilogue
/* 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, const_rtx tem)
+avr_return_addr_rtx (int count, rtx tem)
{
rtx r;
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
HARD_REG_SET set;
int minimize;
HOST_WIDE_INT size = get_frame_size();
- /* Define templates for push instructions. */
- rtx pushbyte = gen_rtx_MEM (QImode,
- gen_rtx_POST_DEC (HImode, stack_pointer_rtx));
- rtx pushword = gen_rtx_MEM (HImode,
- gen_rtx_POST_DEC (HImode, stack_pointer_rtx));
rtx insn;
/* Init cfun->machine. */
if (cfun->machine->is_interrupt || cfun->machine->is_signal)
{
+ /* Enable interrupts. */
if (cfun->machine->is_interrupt)
- {
- /* Enable interrupts. */
- insn = emit_insn (gen_enable_interrupt ());
- RTX_FRAME_RELATED_P (insn) = 1;
- }
+ emit_insn (gen_enable_interrupt ());
/* Push zero reg. */
- insn = emit_move_insn (pushbyte, zero_reg_rtx);
- RTX_FRAME_RELATED_P (insn) = 1;
- cfun->machine->stack_usage++;
+ emit_push_byte (ZERO_REGNO, true);
/* Push tmp reg. */
- insn = emit_move_insn (pushbyte, tmp_reg_rtx);
- RTX_FRAME_RELATED_P (insn) = 1;
- cfun->machine->stack_usage++;
+ emit_push_byte (TMP_REGNO, true);
/* Push SREG. */
- insn = emit_move_insn (tmp_reg_rtx,
- gen_rtx_MEM (QImode, GEN_INT (SREG_ADDR)));
- RTX_FRAME_RELATED_P (insn) = 1;
- insn = emit_move_insn (pushbyte, tmp_reg_rtx);
- RTX_FRAME_RELATED_P (insn) = 1;
- cfun->machine->stack_usage++;
+ /* ??? 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. */
- if(AVR_HAVE_RAMPZ
- && (TEST_HARD_REG_BIT (set, REG_Z) && TEST_HARD_REG_BIT (set, REG_Z + 1)))
+ /* ??? 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))
{
- insn = emit_move_insn (tmp_reg_rtx,
- gen_rtx_MEM (QImode, GEN_INT (RAMPZ_ADDR)));
- RTX_FRAME_RELATED_P (insn) = 1;
- insn = emit_move_insn (pushbyte, tmp_reg_rtx);
- RTX_FRAME_RELATED_P (insn) = 1;
- cfun->machine->stack_usage++;
+ emit_move_insn (tmp_reg_rtx,
+ gen_rtx_MEM (QImode, GEN_INT (RAMPZ_ADDR)));
+ emit_push_byte (TMP_REGNO, false);
}
/* Clear zero reg. */
- insn = emit_move_insn (zero_reg_rtx, const0_rtx);
- RTX_FRAME_RELATED_P (insn) = 1;
+ emit_move_insn (zero_reg_rtx, const0_rtx);
/* Prevent any attempt to delete the setting of ZERO_REG! */
emit_use (zero_reg_rtx);
|| (AVR_2_BYTE_PC && live_seq > 6)
|| live_seq > 7))
{
- insn = emit_move_insn (gen_rtx_REG (HImode, REG_X),
- gen_int_mode (size, HImode));
- RTX_FRAME_RELATED_P (insn) = 1;
+ 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)));
+ 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 of register to save. */
- insn=emit_move_insn (pushbyte, gen_rtx_REG (QImode, reg));
- RTX_FRAME_RELATED_P (insn) = 1;
- cfun->machine->stack_usage++;
- }
- }
+ 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. */
- insn = emit_move_insn (pushword, frame_pointer_rtx);
- RTX_FRAME_RELATED_P (insn) = 1;
- cfun->machine->stack_usage += 2;
+ /* 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)
is selected. */
rtx myfp;
rtx fp_plus_insns;
- rtx sp_plus_insns = NULL_RTX;
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, REGNO (frame_pointer_rtx));
+ /* 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
{
/* Method 1-Adjust frame pointer. */
start_sequence ();
- insn = emit_move_insn (frame_pointer_rtx, stack_pointer_rtx);
- RTX_FRAME_RELATED_P (insn) = 1;
+ /* 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. */
- insn =
- emit_move_insn (myfp,
- gen_rtx_PLUS (GET_MODE(myfp), myfp,
- gen_int_mode (-size,
- GET_MODE(myfp))));
- RTX_FRAME_RELATED_P (insn) = 1;
+ emit_move_insn (frame_pointer_rtx, stack_pointer_rtx);
- /* Copy to stack pointer. */
+ 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)
{
- insn = emit_move_insn (stack_pointer_rtx, frame_pointer_rtx);
- RTX_FRAME_RELATED_P (insn) = 1;
+ emit_move_insn (stack_pointer_rtx, frame_pointer_rtx);
}
else if (TARGET_NO_INTERRUPTS
|| cfun->machine->is_signal
|| cfun->machine->is_OS_main)
{
- insn =
- emit_insn (gen_movhi_sp_r_irq_off (stack_pointer_rtx,
- frame_pointer_rtx));
- RTX_FRAME_RELATED_P (insn) = 1;
+ emit_insn (gen_movhi_sp_r_irq_off (stack_pointer_rtx,
+ frame_pointer_rtx));
}
else if (cfun->machine->is_interrupt)
{
- insn = emit_insn (gen_movhi_sp_r_irq_on (stack_pointer_rtx,
- frame_pointer_rtx));
- RTX_FRAME_RELATED_P (insn) = 1;
+ emit_insn (gen_movhi_sp_r_irq_on (stack_pointer_rtx,
+ frame_pointer_rtx));
}
else
{
- insn = emit_move_insn (stack_pointer_rtx, frame_pointer_rtx);
- RTX_FRAME_RELATED_P (insn) = 1;
+ emit_move_insn (stack_pointer_rtx, frame_pointer_rtx);
}
fp_plus_insns = get_insns ();
/* Method 2-Adjust Stack pointer. */
if (size <= 6)
{
+ rtx sp_plus_insns;
+
start_sequence ();
- insn =
- emit_move_insn (stack_pointer_rtx,
- gen_rtx_PLUS (HImode,
- stack_pointer_rtx,
- gen_int_mode (-size,
- HImode)));
+ 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);
+ 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 (size <= 6 && (get_sequence_length (sp_plus_insns)
- < get_sequence_length (fp_plus_insns)))
- emit_insn (sp_plus_insns);
- else
+ /* 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_info)
+ current_function_static_stack_size = cfun->machine->stack_usage;
}
/* Output summary at end of function prologue. */
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)
+expand_epilogue (bool sibcall_p)
{
int reg;
int live_seq;
/* epilogue: naked */
if (cfun->machine->is_naked)
{
+ gcc_assert (!sibcall_p);
+
emit_jump_insn (gen_return ());
return;
}
/* Try two methods to adjust stack and select shortest. */
rtx myfp;
rtx fp_plus_insns;
- rtx sp_plus_insns = NULL_RTX;
-
+
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, REGNO (frame_pointer_rtx));
+ myfp = gen_rtx_REG (QImode, FRAME_POINTER_REGNUM);
}
else
{
/* Method 1-Adjust frame pointer. */
start_sequence ();
- emit_move_insn (myfp,
- gen_rtx_PLUS (GET_MODE (myfp), myfp,
- gen_int_mode (size,
- GET_MODE(myfp))));
+ emit_move_insn (myfp, plus_constant (myfp, size));
/* Copy to stack pointer. */
if (AVR_HAVE_8BIT_SP)
/* Method 2-Adjust Stack pointer. */
if (size <= 5)
{
+ rtx sp_plus_insns;
+
start_sequence ();
emit_move_insn (stack_pointer_rtx,
- gen_rtx_PLUS (HImode, stack_pointer_rtx,
- gen_int_mode (size,
- HImode)));
+ plus_constant (stack_pointer_rtx, size));
sp_plus_insns = get_insns ();
end_sequence ();
- }
- /* Use shortest method. */
- if (size <= 5 && (get_sequence_length (sp_plus_insns)
- < get_sequence_length (fp_plus_insns)))
- emit_insn (sp_plus_insns);
- else
+ /* 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. */
- emit_insn (gen_pophi (frame_pointer_rtx));
+ /* 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_insn (gen_popqi (gen_rtx_REG (QImode, 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)))
+ if (AVR_HAVE_RAMPZ
+ && TEST_HARD_REG_BIT (set, REG_Z)
+ && TEST_HARD_REG_BIT (set, REG_Z + 1))
{
- emit_insn (gen_popqi (tmp_reg_rtx));
- emit_move_insn (gen_rtx_MEM(QImode, GEN_INT(RAMPZ_ADDR)),
+ 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_insn (gen_popqi (tmp_reg_rtx));
+ emit_pop_byte (TMP_REGNO);
- emit_move_insn (gen_rtx_MEM(QImode, GEN_INT(SREG_ADDR)),
+ emit_move_insn (gen_rtx_MEM (QImode, GEN_INT (SREG_ADDR)),
tmp_reg_rtx);
/* Restore tmp REG. */
- emit_insn (gen_popqi (tmp_reg_rtx));
+ emit_pop_byte (TMP_REGNO);
/* Restore zero REG. */
- emit_insn (gen_popqi (zero_reg_rtx));
+ emit_pop_byte (ZERO_REGNO);
}
- emit_jump_insn (gen_return ());
+ if (!sibcall_p)
+ emit_jump_insn (gen_return ());
}
}
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. */
true_regnum (XEXP (x, 0)));
debug_rtx (x);
}
- if (!strict && GET_CODE (x) == SUBREG)
- x = SUBREG_REG (x);
+
if (REG_P (x) && (strict ? REG_OK_FOR_BASE_STRICT_P (x)
: REG_OK_FOR_BASE_NOSTRICT_P (x)))
r = POINTER_REGS;
}
+/* Helper function to print assembler resp. track instruction
+ sequence lengths.
+
+ If PLEN == NULL:
+ Output assembler code from template TPL with operands supplied
+ by OPERANDS. This is just forwarding to output_asm_insn.
+
+ If PLEN != NULL:
+ Add N_WORDS to *PLEN.
+ Don't output anything.
+*/
+
+static void
+avr_asm_len (const char* tpl, rtx* operands, int* plen, int n_words)
+{
+ if (NULL == plen)
+ {
+ output_asm_insn (tpl, operands);
+ }
+ else
+ {
+ *plen += n_words;
+ }
+}
+
+
/* Return a pointer register name as a string. */
static const char *
if (CONSTANT_ADDRESS_P (addr)
&& text_segment_operand (addr, VOIDmode))
{
- rtx x = XEXP (addr,0);
+ rtx x = addr;
+ if (GET_CODE (x) == CONST)
+ x = XEXP (x, 0);
if (GET_CODE (x) == PLUS && GET_CODE (XEXP (x,1)) == CONST_INT)
{
/* Assembler gs() will implant word address. Make offset
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."))
+ if (warning (0, "pointer offset from symbol maybe incorrect"))
{
output_addr_const (stderr, addr);
fprintf(stderr,"\n");
{
cum->nregs = 18;
cum->regno = FIRST_CUM_REG;
- if (!libname && fntype)
- {
- int stdarg = (TYPE_ARG_TYPES (fntype) != 0
- && (TREE_VALUE (tree_last (TYPE_ARG_TYPES (fntype)))
- != void_type_node));
- if (stdarg)
- cum->nregs = 0;
- }
+ if (!libname && stdarg_p (fntype))
+ cum->nregs = 0;
+
+ /* Assume the calle may be tail called */
+
+ cfun->machine->sibcall_fails = 0;
}
/* Returns the number of registers to allocate for a function argument. */
static int
-avr_num_arg_regs (enum machine_mode mode, tree type)
+avr_num_arg_regs (enum machine_mode mode, const_tree type)
{
int size;
/* Controls whether a function argument is passed
in a register, and which register. */
-rtx
-function_arg (CUMULATIVE_ARGS *cum, enum machine_mode mode, tree type,
- int named ATTRIBUTE_UNUSED)
+static rtx
+avr_function_arg (cumulative_args_t cum_v, enum machine_mode mode,
+ const_tree type, bool named ATTRIBUTE_UNUSED)
{
+ CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v);
int bytes = avr_num_arg_regs (mode, type);
if (cum->nregs && bytes <= cum->nregs)
/* Update the summarizer variable CUM to advance past an argument
in the argument list. */
-void
-function_arg_advance (CUMULATIVE_ARGS *cum, enum machine_mode mode, tree type,
- int named ATTRIBUTE_UNUSED)
+static void
+avr_function_arg_advance (cumulative_args_t cum_v, enum machine_mode mode,
+ const_tree type, bool named ATTRIBUTE_UNUSED)
{
+ CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v);
int bytes = avr_num_arg_regs (mode, type);
cum->nregs -= bytes;
cum->regno -= bytes;
+ /* A parameter is being passed in a call-saved register. As the original
+ contents of these regs has to be restored before leaving the function,
+ a function must not pass arguments in call-saved regs in order to get
+ tail-called. */
+
+ if (cum->regno >= 8
+ && cum->nregs >= 0
+ && !call_used_regs[cum->regno])
+ {
+ /* FIXME: We ship info on failing tail-call in struct machine_function.
+ This uses internals of calls.c:expand_call() and the way args_so_far
+ is used. targetm.function_ok_for_sibcall() needs to be extended to
+ pass &args_so_far, too. At present, CUMULATIVE_ARGS is target
+ dependent so that such an extension is not wanted. */
+
+ cfun->machine->sibcall_fails = 1;
+ }
+
+ /* Test if all registers needed by the ABI are actually available. If the
+ user has fixed a GPR needed to pass an argument, an (implicit) function
+ call would clobber that fixed register. See PR45099 for an example. */
+
+ if (cum->regno >= 8
+ && cum->nregs >= 0)
+ {
+ int regno;
+
+ for (regno = cum->regno; regno < cum->regno + bytes; regno++)
+ if (fixed_regs[regno])
+ error ("Register %s is needed to pass a parameter but is fixed",
+ reg_names[regno]);
+ }
+
if (cum->nregs <= 0)
{
cum->nregs = 0;
}
}
+/* Implement `TARGET_FUNCTION_OK_FOR_SIBCALL' */
+/* Decide whether we can make a sibling call to a function. DECL is the
+ declaration of the function being targeted by the call and EXP is the
+ CALL_EXPR representing the call. */
+
+static bool
+avr_function_ok_for_sibcall (tree decl_callee, tree exp_callee)
+{
+ tree fntype_callee;
+
+ /* Tail-calling must fail if callee-saved regs are used to pass
+ function args. We must not tail-call when `epilogue_restores'
+ is used. Unfortunately, we cannot tell at this point if that
+ actually will happen or not, and we cannot step back from
+ tail-calling. Thus, we inhibit tail-calling with -mcall-prologues. */
+
+ if (cfun->machine->sibcall_fails
+ || TARGET_CALL_PROLOGUES)
+ {
+ return false;
+ }
+
+ fntype_callee = TREE_TYPE (CALL_EXPR_FN (exp_callee));
+
+ if (decl_callee)
+ {
+ decl_callee = TREE_TYPE (decl_callee);
+ }
+ else
+ {
+ decl_callee = fntype_callee;
+
+ while (FUNCTION_TYPE != TREE_CODE (decl_callee)
+ && METHOD_TYPE != TREE_CODE (decl_callee))
+ {
+ decl_callee = TREE_TYPE (decl_callee);
+ }
+ }
+
+ /* Ensure that caller and callee have compatible epilogues */
+
+ if (interrupt_function_p (current_function_decl)
+ || signal_function_p (current_function_decl)
+ || avr_naked_function_p (decl_callee)
+ || avr_naked_function_p (current_function_decl)
+ /* FIXME: For OS_task and OS_main, we are over-conservative.
+ This is due to missing documentation of these attributes
+ and what they actually should do and should not do. */
+ || (avr_OS_task_function_p (decl_callee)
+ != avr_OS_task_function_p (current_function_decl))
+ || (avr_OS_main_function_p (decl_callee)
+ != avr_OS_main_function_p (current_function_decl)))
+ {
+ return false;
+ }
+
+ return true;
+}
+
/***********************************************************************
Functions for outputting various mov's for a various modes
************************************************************************/
}
const char *
-output_movsisf(rtx insn, rtx operands[], int *l)
+output_movsisf (rtx insn, rtx operands[], rtx clobber_reg, int *l)
{
int dummy;
rtx dest = operands[0];
AS2 (mov,%D0,%D1));
}
}
+ else if (CONST_INT_P (src)
+ || CONST_DOUBLE_P (src))
+ {
+ return output_reload_insisf (insn, operands, clobber_reg, real_l);
+ }
else if (CONSTANT_P (src))
{
if (test_hard_reg_class (LD_REGS, dest)) /* ldi d,i */
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
{
const char *templ;
- if (src == const0_rtx)
+ if (src == CONST0_RTX (GET_MODE (dest)))
operands[1] = zero_reg_rtx;
templ = out_movsi_mr_r (insn, operands, real_l);
return "";
}
-/* Modifies the length assigned to instruction INSN
- LEN is the initially computed length of the insn. */
+/* 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 (GET_MODE (scratch) == HImode && move_mode == QImode)
+ 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. */
+
+ 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)
break;
case SImode:
case SFmode:
- output_movsisf (insn, op, &len);
+ output_movsisf (insn, op, NULL_RTX, &len);
break;
default:
break;
break;
case SImode:
case SFmode:
- output_reload_insisf (insn, op, &len);
+ output_reload_insisf (insn, op, XEXP (op[2], 0), &len);
break;
default:
break;
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. */
-bool
-class_likely_spilled_p (int c)
+static bool
+avr_class_likely_spilled_p (reg_class_t c)
{
return (c != ALL_REGS && c != ADDW_REGS);
}
}
else if (TREE_STATIC (*node) || DECL_EXTERNAL (*node))
{
- if (DECL_INITIAL (*node) == NULL_TREE && !DECL_EXTERNAL (*node))
- {
- warning (0, "only initialized variables can be placed into "
- "program memory area");
- *no_add_attrs = true;
- }
+ *no_add_attrs = false;
}
else
{
&& (TREE_STATIC (node) || DECL_EXTERNAL (node))
&& avr_progmem_p (node, *attributes))
{
- static const char dsec[] = ".progmem.data";
- *attributes = tree_cons (get_identifier ("section"),
- build_tree_list (NULL, build_string (strlen (dsec), dsec)),
- *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);
- /* ??? This seems sketchy. Why can't the user declare the
- thing const in the first place? */
- TREE_READONLY (node) = 1;
+ 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);
+ }
}
}
fprintf (asm_out_file, "\t.p2align 1\n");
}
-/* Implement TARGET_ASM_INIT_SECTIONS. */
+
+/* Implement `ASM_OUTPUT_ALIGNED_DECL_LOCAL'. */
+/* Implement `ASM_OUTPUT_ALIGNED_DECL_COMMON'. */
+/* Track need of __do_clear_bss. */
+
+void
+avr_asm_output_aligned_decl_common (FILE * stream, const_tree decl ATTRIBUTE_UNUSED,
+ const char *name, unsigned HOST_WIDE_INT size,
+ unsigned int align, bool local_p)
+{
+ avr_need_clear_bss_p = true;
+
+ if (local_p)
+ ASM_OUTPUT_ALIGNED_LOCAL (stream, name, size, align);
+ else
+ ASM_OUTPUT_ALIGNED_COMMON (stream, name, size, align);
+}
+
+
+/* Unnamed section callback for data_section
+ to track need of __do_copy_data. */
+
+static void
+avr_output_data_section_asm_op (const void *data)
+{
+ avr_need_copy_data_p = true;
+
+ /* Dispatch to default. */
+ output_section_asm_op (data);
+}
+
+
+/* Unnamed section callback for bss_section
+ to track need of __do_clear_bss. */
+
+static void
+avr_output_bss_section_asm_op (const void *data)
+{
+ avr_need_clear_bss_p = true;
+
+ /* Dispatch to default. */
+ output_section_asm_op (data);
+}
+
+
+/* Implement `TARGET_ASM_INIT_SECTIONS'. */
static void
avr_asm_init_sections (void)
avr_output_progmem_section_asm_op,
NULL);
readonly_data_section = data_section;
+
+ data_section->unnamed.callback = avr_output_data_section_asm_op;
+ bss_section->unnamed.callback = avr_output_bss_section_asm_op;
+}
+
+
+/* Implement `TARGET_ASM_NAMED_SECTION'. */
+/* Track need of __do_clear_bss, __do_copy_data for named sections. */
+
+void
+avr_asm_named_section (const char *name, unsigned int flags, tree decl)
+{
+ if (!avr_need_copy_data_p)
+ avr_need_copy_data_p = (0 == strncmp (name, ".data", 5)
+ || 0 == strncmp (name, ".rodata", 7)
+ || 0 == strncmp (name, ".gnu.linkonce.d", 15));
+
+ if (!avr_need_clear_bss_p)
+ avr_need_clear_bss_p = (0 == strncmp (name, ".bss", 4));
+
+ default_elf_asm_named_section (name, flags, decl);
}
static unsigned int
".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);
+}
+
+
+/* Implement `TARGET_ASM_FILE_START'. */
/* Outputs some appropriate text to go at the start of an assembler
file. */
avr_file_start (void)
{
if (avr_current_arch->asm_only)
- error ("MCU %qs supported for assembler only", avr_mcu_name);
+ error ("MCU %qs supported for assembler only", avr_current_device->name);
default_file_start ();
-/* fprintf (asm_out_file, "\t.arch %s\n", avr_mcu_name);*/
+/* fprintf (asm_out_file, "\t.arch %s\n", avr_current_device->name);*/
fputs ("__SREG__ = 0x3f\n"
"__SP_H__ = 0x3e\n"
"__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);
}
+
+/* Implement `TARGET_ASM_FILE_END'. */
/* Outputs to the stdio stream FILE some
appropriate text to go at the end of an assembler file. */
static void
avr_file_end (void)
{
+ /* Output these only if there is anything in the
+ .data* / .rodata* / .gnu.linkonce.* resp. .bss*
+ input section(s) - some code size can be saved by not
+ linking in the initialization code from libgcc if resp.
+ sections are empty. */
+
+ if (avr_need_copy_data_p)
+ fputs (".global __do_copy_data\n", asm_out_file);
+
+ if (avr_need_clear_bss_p)
+ fputs (".global __do_clear_bss\n", asm_out_file);
}
/* Choose the order in which to allocate hard registers for
}
+/* 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
/* Returns register number for function return value.*/
-int
+static inline unsigned int
avr_ret_register (void)
{
return 24;
}
+/* Worker function for TARGET_FUNCTION_VALUE_REGNO_P. */
+
+static bool
+avr_function_value_regno_p (const unsigned int regno)
+{
+ return (regno == avr_ret_register ());
+}
+
/* Create an RTX representing the place where a
library function returns a value of mode MODE. */
-rtx
-avr_libcall_value (enum machine_mode mode)
+static rtx
+avr_libcall_value (enum machine_mode mode,
+ const_rtx func ATTRIBUTE_UNUSED)
{
int offs = GET_MODE_SIZE (mode);
if (offs < 2)
offs = 2;
- return gen_rtx_REG (mode, RET_REGISTER + 2 - offs);
+ return gen_rtx_REG (mode, avr_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)
+static rtx
+avr_function_value (const_tree type,
+ const_tree fn_decl_or_type ATTRIBUTE_UNUSED,
+ bool outgoing ATTRIBUTE_UNUSED)
{
unsigned int offs;
-
+
if (TYPE_MODE (type) != BLKmode)
- return avr_libcall_value (TYPE_MODE (type));
+ return avr_libcall_value (TYPE_MODE (type), NULL_RTX);
offs = int_size_in_bytes (type);
if (offs < 2)
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);
-}
-
-/* Places additional restrictions on the register class to
- use when it is necessary to copy value X into a register
- in class CLASS. */
-
-enum reg_class
-preferred_reload_class (rtx x ATTRIBUTE_UNUSED, enum reg_class rclass)
-{
- return rclass;
+ return gen_rtx_REG (BLKmode, avr_ret_register () + 2 - offs);
}
int
int
avr_hard_regno_mode_ok (int regno, enum machine_mode mode)
{
- /* Disallow QImode in stack pointer regs. */
- if ((regno == REG_SP || regno == (REG_SP + 1)) && mode == QImode)
- return 0;
-
- /* The only thing that can go into registers r28:r29 is a Pmode. */
- if (regno == REG_Y && mode == Pmode)
- return 1;
-
- /* Otherwise disallow all regno/mode combinations that span r28:r29. */
- if (regno <= (REG_Y + 1) && (regno + GET_MODE_SIZE (mode)) >= (REG_Y + 1))
- return 0;
-
- if (mode == QImode)
+ /* 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;
- /* Modes larger than QImode occupy consecutive registers. */
- if (regno + GET_MODE_SIZE (mode) > FIRST_PSEUDO_REGISTER)
+ /* 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 QImode should start in an even register. */
+ /* All modes larger than 8 bits should start in an even register. */
+
return !(regno & 1);
}
}
+/* Reload a SI or SF compile time constant (OP[1]) into a GPR (OP[0]).
+ CLOBBER_REG is a QI clobber reg needed to move vast majority of consts
+ into a NO_LD_REGS. If CLOBBER_REG is NULL_RTX we either don't need a
+ clobber reg or have to cook one up.
+
+ LEN == NULL: Output instructions.
+
+ LEN != NULL: Output nothing. Increment *LEN by number of words occupied
+ by the insns printed.
+
+ Return "". */
+
const char *
-output_reload_insisf (rtx insn ATTRIBUTE_UNUSED, rtx *operands, int *len)
+output_reload_insisf (rtx insn ATTRIBUTE_UNUSED,
+ rtx *op, rtx clobber_reg, int *len)
{
- rtx src = operands[1];
- int cnst = (GET_CODE (src) == CONST_INT);
+ rtx src = op[1];
+ rtx dest = op[0];
+ rtx xval, xdest[4];
+ int ival[4];
+ int clobber_val = 1234;
+ bool cooked_clobber_p = false;
+ bool set_p = false;
+ unsigned int n;
+ enum machine_mode mode = GET_MODE (dest);
+
+ gcc_assert (REG_P (dest));
if (len)
+ *len = 0;
+
+ /* (REG:SI 14) is special: It's neither in LD_REGS nor in NO_LD_REGS
+ but has some subregs that are in LD_REGS. Use the MSB (REG:QI 17). */
+
+ if (14 == REGNO (dest))
{
- if (cnst)
- *len = 4 + ((INTVAL (src) & 0xff) != 0)
- + ((INTVAL (src) & 0xff00) != 0)
- + ((INTVAL (src) & 0xff0000) != 0)
- + ((INTVAL (src) & 0xff000000) != 0);
- else
- *len = 8;
-
- return "";
+ clobber_reg = gen_rtx_REG (QImode, 17);
}
- 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
+ /* We might need a clobber reg but don't have one. Look at the value
+ to be loaded more closely. A clobber is only needed if it contains
+ a byte that is neither 0, -1 or a power of 2. */
+
+ if (NULL_RTX == clobber_reg
+ && !test_hard_reg_class (LD_REGS, dest))
{
- output_asm_insn (AS2 (ldi, %2, hi8(%1)), operands);
- output_asm_insn (AS2 (mov, %B0, %2), operands);
+ for (n = 0; n < GET_MODE_SIZE (mode); n++)
+ {
+ xval = simplify_gen_subreg (QImode, src, mode, n);
+
+ if (!(const0_rtx == xval
+ || constm1_rtx == xval
+ || single_one_operand (xval, QImode)))
+ {
+ /* We have no clobber reg but need one. Cook one up.
+ That's cheaper than loading from constant pool. */
+
+ cooked_clobber_p = true;
+ clobber_reg = gen_rtx_REG (QImode, REG_Z + 1);
+ avr_asm_len ("mov __tmp_reg__,%0", &clobber_reg, len, 1);
+ break;
+ }
+ }
}
- if (cnst && ((INTVAL (src) & 0xff0000) == 0))
- output_asm_insn (AS2 (mov, %C0, __zero_reg__), operands);
- else
+
+ /* Now start filling DEST from LSB to MSB. */
+
+ for (n = 0; n < GET_MODE_SIZE (mode); n++)
{
- output_asm_insn (AS2 (ldi, %2, hlo8(%1)), operands);
- output_asm_insn (AS2 (mov, %C0, %2), operands);
+ bool done_byte = false;
+ unsigned int j;
+ rtx xop[3];
+
+ /* Crop the n-th sub-byte. */
+
+ xval = simplify_gen_subreg (QImode, src, mode, n);
+ xdest[n] = simplify_gen_subreg (QImode, dest, mode, n);
+ ival[n] = INTVAL (xval);
+
+ /* Look if we can reuse the low word by means of MOVW. */
+
+ if (n == 2
+ && AVR_HAVE_MOVW)
+ {
+ rtx lo16 = simplify_gen_subreg (HImode, src, mode, 0);
+ rtx hi16 = simplify_gen_subreg (HImode, src, mode, 2);
+
+ if (INTVAL (lo16) == INTVAL (hi16))
+ {
+ avr_asm_len ("movw %C0,%A0", &op[0], len, 1);
+ break;
+ }
+ }
+
+ /* Use CLR to zero a value so that cc0 is set as expected
+ for zero. */
+
+ if (ival[n] == 0)
+ {
+ avr_asm_len ("clr %0", &xdest[n], len, 1);
+ continue;
+ }
+
+ if (clobber_val == ival[n]
+ && REGNO (clobber_reg) == REGNO (xdest[n]))
+ {
+ continue;
+ }
+
+ /* LD_REGS can use LDI to move a constant value */
+
+ if (test_hard_reg_class (LD_REGS, xdest[n]))
+ {
+ xop[0] = xdest[n];
+ xop[1] = xval;
+ avr_asm_len ("ldi %0,lo8(%1)", xop, len, 1);
+ continue;
+ }
+
+ /* Try to reuse value already loaded in some lower byte. */
+
+ for (j = 0; j < n; j++)
+ if (ival[j] == ival[n])
+ {
+ xop[0] = xdest[n];
+ xop[1] = xdest[j];
+
+ avr_asm_len ("mov %0,%1", xop, len, 1);
+ done_byte = true;
+ break;
+ }
+
+ if (done_byte)
+ continue;
+
+ /* Need no clobber reg for -1: Use CLR/DEC */
+
+ if (-1 == ival[n])
+ {
+ avr_asm_len ("clr %0" CR_TAB
+ "dec %0", &xdest[n], len, 2);
+ continue;
+ }
+
+ /* Use T flag or INC to manage powers of 2 if we have
+ no clobber reg. */
+
+ if (NULL_RTX == clobber_reg
+ && single_one_operand (xval, QImode))
+ {
+ if (1 == ival[n])
+ {
+ avr_asm_len ("clr %0" CR_TAB
+ "inc %0", &xdest[n], len, 2);
+ continue;
+ }
+
+ xop[0] = xdest[n];
+ xop[1] = GEN_INT (exact_log2 (ival[n] & GET_MODE_MASK (QImode)));
+
+ gcc_assert (constm1_rtx != xop[1]);
+
+ if (!set_p)
+ {
+ set_p = true;
+ avr_asm_len ("set", xop, len, 1);
+ }
+
+ avr_asm_len ("clr %0" CR_TAB
+ "bld %0,%1", xop, len, 2);
+ continue;
+ }
+
+ /* We actually need the LD_REGS clobber reg. */
+
+ gcc_assert (NULL_RTX != clobber_reg);
+
+ xop[0] = xdest[n];
+ xop[1] = xval;
+ xop[2] = clobber_reg;
+ clobber_val = ival[n];
+
+ avr_asm_len ("ldi %2,lo8(%1)" CR_TAB
+ "mov %0,%2", xop, len, 2);
}
- if (cnst && ((INTVAL (src) & 0xff000000) == 0))
- output_asm_insn (AS2 (mov, %D0, __zero_reg__), operands);
- else
+
+ /* If we cooked up a clobber reg above, restore it. */
+
+ if (cooked_clobber_p)
{
- output_asm_insn (AS2 (ldi, %2, hhi8(%1)), operands);
- output_asm_insn (AS2 (mov, %D0, %2), operands);
+ avr_asm_len ("mov %0,__tmp_reg__", &clobber_reg, len, 1);
}
+
return "";
}
&& !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 ATTRIBUTE_UNUSED,
+avr_hard_regno_rename_ok (unsigned int old_reg,
unsigned int new_reg)
{
/* Interrupt functions can only use registers that have already been
&& !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;
}
return (!AVR_HAVE_JMP_CALL || TARGET_CALL_PROLOGUES) ? 8 : 17;
}
+/* Helper for __builtin_avr_delay_cycles */
+
+static void
+avr_expand_delay_cycles (rtx operands0)
+{
+ unsigned HOST_WIDE_INT cycles = UINTVAL (operands0);
+ unsigned HOST_WIDE_INT cycles_used;
+ unsigned HOST_WIDE_INT loop_count;
+
+ if (IN_RANGE (cycles, 83886082, 0xFFFFFFFF))
+ {
+ loop_count = ((cycles - 9) / 6) + 1;
+ cycles_used = ((loop_count - 1) * 6) + 9;
+ emit_insn (gen_delay_cycles_4 (gen_int_mode (loop_count, SImode)));
+ cycles -= cycles_used;
+ }
+
+ if (IN_RANGE (cycles, 262145, 83886081))
+ {
+ loop_count = ((cycles - 7) / 5) + 1;
+ if (loop_count > 0xFFFFFF)
+ loop_count = 0xFFFFFF;
+ cycles_used = ((loop_count - 1) * 5) + 7;
+ emit_insn (gen_delay_cycles_3 (gen_int_mode (loop_count, SImode)));
+ cycles -= cycles_used;
+ }
+
+ if (IN_RANGE (cycles, 768, 262144))
+ {
+ loop_count = ((cycles - 5) / 4) + 1;
+ if (loop_count > 0xFFFF)
+ loop_count = 0xFFFF;
+ cycles_used = ((loop_count - 1) * 4) + 5;
+ emit_insn (gen_delay_cycles_2 (gen_int_mode (loop_count, HImode)));
+ cycles -= cycles_used;
+ }
+
+ if (IN_RANGE (cycles, 6, 767))
+ {
+ loop_count = cycles / 3;
+ if (loop_count > 255)
+ loop_count = 255;
+ cycles_used = loop_count * 3;
+ emit_insn (gen_delay_cycles_1 (gen_int_mode (loop_count, QImode)));
+ cycles -= cycles_used;
+ }
+
+ while (cycles >= 2)
+ {
+ emit_insn (gen_nopv (GEN_INT(2)));
+ cycles -= 2;
+ }
+
+ if (cycles == 1)
+ {
+ emit_insn (gen_nopv (GEN_INT(1)));
+ cycles--;
+ }
+}
+
+/* IDs for all the AVR builtins. */
+
+enum avr_builtin_id
+ {
+ AVR_BUILTIN_NOP,
+ AVR_BUILTIN_SEI,
+ AVR_BUILTIN_CLI,
+ AVR_BUILTIN_WDR,
+ AVR_BUILTIN_SLEEP,
+ AVR_BUILTIN_SWAP,
+ AVR_BUILTIN_FMUL,
+ AVR_BUILTIN_FMULS,
+ AVR_BUILTIN_FMULSU,
+ AVR_BUILTIN_DELAY_CYCLES
+ };
+
+#define DEF_BUILTIN(NAME, TYPE, CODE) \
+ do \
+ { \
+ add_builtin_function ((NAME), (TYPE), (CODE), BUILT_IN_MD, \
+ NULL, NULL_TREE); \
+ } while (0)
+
+
+/* Implement `TARGET_INIT_BUILTINS' */
+/* Set up all builtin functions for this target. */
+
+static void
+avr_init_builtins (void)
+{
+ tree void_ftype_void
+ = build_function_type_list (void_type_node, NULL_TREE);
+ tree uchar_ftype_uchar
+ = build_function_type_list (unsigned_char_type_node,
+ unsigned_char_type_node,
+ NULL_TREE);
+ tree uint_ftype_uchar_uchar
+ = build_function_type_list (unsigned_type_node,
+ unsigned_char_type_node,
+ unsigned_char_type_node,
+ NULL_TREE);
+ tree int_ftype_char_char
+ = build_function_type_list (integer_type_node,
+ char_type_node,
+ char_type_node,
+ NULL_TREE);
+ tree int_ftype_char_uchar
+ = build_function_type_list (integer_type_node,
+ char_type_node,
+ unsigned_char_type_node,
+ NULL_TREE);
+ tree void_ftype_ulong
+ = build_function_type_list (void_type_node,
+ long_unsigned_type_node,
+ NULL_TREE);
+
+ DEF_BUILTIN ("__builtin_avr_nop", void_ftype_void, AVR_BUILTIN_NOP);
+ DEF_BUILTIN ("__builtin_avr_sei", void_ftype_void, AVR_BUILTIN_SEI);
+ DEF_BUILTIN ("__builtin_avr_cli", void_ftype_void, AVR_BUILTIN_CLI);
+ DEF_BUILTIN ("__builtin_avr_wdr", void_ftype_void, AVR_BUILTIN_WDR);
+ DEF_BUILTIN ("__builtin_avr_sleep", void_ftype_void, AVR_BUILTIN_SLEEP);
+ DEF_BUILTIN ("__builtin_avr_swap", uchar_ftype_uchar, AVR_BUILTIN_SWAP);
+ DEF_BUILTIN ("__builtin_avr_delay_cycles", void_ftype_ulong,
+ AVR_BUILTIN_DELAY_CYCLES);
+
+ DEF_BUILTIN ("__builtin_avr_fmul", uint_ftype_uchar_uchar,
+ AVR_BUILTIN_FMUL);
+ DEF_BUILTIN ("__builtin_avr_fmuls", int_ftype_char_char,
+ AVR_BUILTIN_FMULS);
+ DEF_BUILTIN ("__builtin_avr_fmulsu", int_ftype_char_uchar,
+ AVR_BUILTIN_FMULSU);
+}
+
+#undef DEF_BUILTIN
+
+struct avr_builtin_description
+{
+ const enum insn_code icode;
+ const char *const name;
+ const enum avr_builtin_id id;
+};
+
+static const struct avr_builtin_description
+bdesc_1arg[] =
+ {
+ { CODE_FOR_rotlqi3_4, "__builtin_avr_swap", AVR_BUILTIN_SWAP }
+ };
+
+static const struct avr_builtin_description
+bdesc_2arg[] =
+ {
+ { CODE_FOR_fmul, "__builtin_avr_fmul", AVR_BUILTIN_FMUL },
+ { CODE_FOR_fmuls, "__builtin_avr_fmuls", AVR_BUILTIN_FMULS },
+ { CODE_FOR_fmulsu, "__builtin_avr_fmulsu", AVR_BUILTIN_FMULSU }
+ };
+
+/* Subroutine of avr_expand_builtin to take care of unop insns. */
+
+static rtx
+avr_expand_unop_builtin (enum insn_code icode, tree exp,
+ rtx target)
+{
+ rtx pat;
+ tree arg0 = CALL_EXPR_ARG (exp, 0);
+ rtx op0 = expand_expr (arg0, NULL_RTX, VOIDmode, EXPAND_NORMAL);
+ enum machine_mode op0mode = GET_MODE (op0);
+ enum machine_mode tmode = insn_data[icode].operand[0].mode;
+ enum machine_mode mode0 = insn_data[icode].operand[1].mode;
+
+ if (! target
+ || GET_MODE (target) != tmode
+ || ! (*insn_data[icode].operand[0].predicate) (target, tmode))
+ {
+ target = gen_reg_rtx (tmode);
+ }
+
+ if (op0mode == SImode && mode0 == HImode)
+ {
+ op0mode = HImode;
+ op0 = gen_lowpart (HImode, op0);
+ }
+
+ gcc_assert (op0mode == mode0 || op0mode == VOIDmode);
+
+ if (! (*insn_data[icode].operand[1].predicate) (op0, mode0))
+ op0 = copy_to_mode_reg (mode0, op0);
+
+ pat = GEN_FCN (icode) (target, op0);
+ if (! pat)
+ return 0;
+
+ emit_insn (pat);
+
+ return target;
+}
+
+
+/* Subroutine of avr_expand_builtin to take care of binop insns. */
+
+static rtx
+avr_expand_binop_builtin (enum insn_code icode, tree exp, rtx target)
+{
+ rtx pat;
+ tree arg0 = CALL_EXPR_ARG (exp, 0);
+ tree arg1 = CALL_EXPR_ARG (exp, 1);
+ rtx op0 = expand_expr (arg0, NULL_RTX, VOIDmode, EXPAND_NORMAL);
+ rtx op1 = expand_expr (arg1, NULL_RTX, VOIDmode, EXPAND_NORMAL);
+ enum machine_mode op0mode = GET_MODE (op0);
+ enum machine_mode op1mode = GET_MODE (op1);
+ enum machine_mode tmode = insn_data[icode].operand[0].mode;
+ enum machine_mode mode0 = insn_data[icode].operand[1].mode;
+ enum machine_mode mode1 = insn_data[icode].operand[2].mode;
+
+ if (! target
+ || GET_MODE (target) != tmode
+ || ! (*insn_data[icode].operand[0].predicate) (target, tmode))
+ {
+ target = gen_reg_rtx (tmode);
+ }
+
+ if ((op0mode == SImode || op0mode == VOIDmode) && mode0 == HImode)
+ {
+ op0mode = HImode;
+ op0 = gen_lowpart (HImode, op0);
+ }
+
+ if ((op1mode == SImode || op1mode == VOIDmode) && mode1 == HImode)
+ {
+ op1mode = HImode;
+ op1 = gen_lowpart (HImode, op1);
+ }
+
+ /* In case the insn wants input operands in modes different from
+ the result, abort. */
+
+ gcc_assert ((op0mode == mode0 || op0mode == VOIDmode)
+ && (op1mode == mode1 || op1mode == VOIDmode));
+
+ if (! (*insn_data[icode].operand[1].predicate) (op0, mode0))
+ op0 = copy_to_mode_reg (mode0, op0);
+
+ if (! (*insn_data[icode].operand[2].predicate) (op1, mode1))
+ op1 = copy_to_mode_reg (mode1, op1);
+
+ pat = GEN_FCN (icode) (target, op0, op1);
+
+ if (! pat)
+ return 0;
+
+ emit_insn (pat);
+ return target;
+}
+
+
+/* Expand an expression EXP that calls a built-in function,
+ with result going to TARGET if that's convenient
+ (and in mode MODE if that's convenient).
+ SUBTARGET may be used as the target for computing one of EXP's operands.
+ IGNORE is nonzero if the value is to be ignored. */
+
+static rtx
+avr_expand_builtin (tree exp, rtx target,
+ rtx subtarget ATTRIBUTE_UNUSED,
+ enum machine_mode mode ATTRIBUTE_UNUSED,
+ int ignore ATTRIBUTE_UNUSED)
+{
+ size_t i;
+ const struct avr_builtin_description *d;
+ tree fndecl = TREE_OPERAND (CALL_EXPR_FN (exp), 0);
+ unsigned int id = DECL_FUNCTION_CODE (fndecl);
+ tree arg0;
+ rtx op0;
+
+ switch (id)
+ {
+ case AVR_BUILTIN_NOP:
+ emit_insn (gen_nopv (GEN_INT(1)));
+ return 0;
+
+ case AVR_BUILTIN_SEI:
+ emit_insn (gen_enable_interrupt ());
+ return 0;
+
+ case AVR_BUILTIN_CLI:
+ emit_insn (gen_disable_interrupt ());
+ return 0;
+
+ case AVR_BUILTIN_WDR:
+ emit_insn (gen_wdr ());
+ return 0;
+
+ case AVR_BUILTIN_SLEEP:
+ emit_insn (gen_sleep ());
+ return 0;
+
+ case AVR_BUILTIN_DELAY_CYCLES:
+ {
+ arg0 = CALL_EXPR_ARG (exp, 0);
+ op0 = expand_expr (arg0, NULL_RTX, VOIDmode, EXPAND_NORMAL);
+
+ if (! CONST_INT_P (op0))
+ error ("__builtin_avr_delay_cycles expects a compile time integer constant.");
+
+ avr_expand_delay_cycles (op0);
+ return 0;
+ }
+ }
+
+ for (i = 0, d = bdesc_1arg; i < ARRAY_SIZE (bdesc_1arg); i++, d++)
+ if (d->id == id)
+ return avr_expand_unop_builtin (d->icode, exp, target);
+
+ for (i = 0, d = bdesc_2arg; i < ARRAY_SIZE (bdesc_2arg); i++, d++)
+ if (d->id == id)
+ return avr_expand_binop_builtin (d->icode, exp, target);
+
+ gcc_unreachable ();
+}
+
+
#include "gt-avr.h"