blob: b4f41ead478fb13413af2401dff56eda778e577a [file] [log] [blame]
/* Subroutines for insn-output.c for ATMEL AVR micro controllers
Copyright (C) 1998-2015 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 "hash-table.h"
#include "tm.h"
#include "hard-reg-set.h"
#include "rtl.h"
#include "hash-set.h"
#include "symtab.h"
#include "inchash.h"
#include "tree.h"
#include "function.h"
#include "hash-map.h"
#include "plugin-api.h"
#include "ipa-ref.h"
#include "cgraph.h"
#include "regs.h"
#include "insn-config.h"
#include "conditions.h"
#include "insn-attr.h"
#include "insn-codes.h"
#include "flags.h"
#include "reload.h"
#include "hash-set.h"
#include "machmode.h"
#include "vec.h"
#include "double-int.h"
#include "input.h"
#include "alias.h"
#include "symtab.h"
#include "wide-int.h"
#include "inchash.h"
#include "tree.h"
#include "fold-const.h"
#include "varasm.h"
#include "print-tree.h"
#include "calls.h"
#include "stor-layout.h"
#include "stringpool.h"
#include "output.h"
#include "hashtab.h"
#include "function.h"
#include "statistics.h"
#include "real.h"
#include "fixed-value.h"
#include "expmed.h"
#include "dojump.h"
#include "explow.h"
#include "emit-rtl.h"
#include "stmt.h"
#include "expr.h"
#include "c-family/c-common.h"
#include "diagnostic-core.h"
#include "obstack.h"
#include "recog.h"
#include "optabs.h"
#include "ggc.h"
#include "langhooks.h"
#include "tm_p.h"
#include "target.h"
#include "target-def.h"
#include "params.h"
#include "dominance.h"
#include "cfg.h"
#include "cfgrtl.h"
#include "cfganal.h"
#include "lcm.h"
#include "cfgbuild.h"
#include "cfgcleanup.h"
#include "predict.h"
#include "basic-block.h"
#include "df.h"
#include "builtins.h"
#include "context.h"
#include "tree-pass.h"
/* Maximal allowed offset for an address in the LD command */
#define MAX_LD_OFFSET(MODE) (64 - (signed)GET_MODE_SIZE (MODE))
/* Return true if STR starts with PREFIX and false, otherwise. */
#define STR_PREFIX_P(STR,PREFIX) (0 == strncmp (STR, PREFIX, strlen (PREFIX)))
/* The 4 bits starting at SECTION_MACH_DEP are reserved to store the
address space where data is to be located.
As the only non-generic address spaces are all located in flash,
this can be used to test if data shall go into some .progmem* section.
This must be the rightmost field of machine dependent section flags. */
#define AVR_SECTION_PROGMEM (0xf * SECTION_MACH_DEP)
/* Similar 4-bit region for SYMBOL_REF_FLAGS. */
#define AVR_SYMBOL_FLAG_PROGMEM (0xf * SYMBOL_FLAG_MACH_DEP)
/* Similar 4-bit region in SYMBOL_REF_FLAGS:
Set address-space AS in SYMBOL_REF_FLAGS of SYM */
#define AVR_SYMBOL_SET_ADDR_SPACE(SYM,AS) \
do { \
SYMBOL_REF_FLAGS (sym) &= ~AVR_SYMBOL_FLAG_PROGMEM; \
SYMBOL_REF_FLAGS (sym) |= (AS) * SYMBOL_FLAG_MACH_DEP; \
} while (0)
/* Read address-space from SYMBOL_REF_FLAGS of SYM */
#define AVR_SYMBOL_GET_ADDR_SPACE(SYM) \
((SYMBOL_REF_FLAGS (sym) & AVR_SYMBOL_FLAG_PROGMEM) \
/ SYMBOL_FLAG_MACH_DEP)
#define TINY_ADIW(REG1, REG2, I) \
"subi " #REG1 ",lo8(-(" #I "))" CR_TAB \
"sbci " #REG2 ",hi8(-(" #I "))"
#define TINY_SBIW(REG1, REG2, I) \
"subi " #REG1 ",lo8((" #I "))" CR_TAB \
"sbci " #REG2 ",hi8((" #I "))"
#define AVR_TMP_REGNO (AVR_TINY ? TMP_REGNO_TINY : TMP_REGNO)
#define AVR_ZERO_REGNO (AVR_TINY ? ZERO_REGNO_TINY : ZERO_REGNO)
/* Known address spaces. The order must be the same as in the respective
enum from avr.h (or designated initialized must be used). */
const avr_addrspace_t avr_addrspace[ADDR_SPACE_COUNT] =
{
{ ADDR_SPACE_RAM, 0, 2, "", 0, NULL },
{ ADDR_SPACE_FLASH, 1, 2, "__flash", 0, ".progmem.data" },
{ ADDR_SPACE_FLASH1, 1, 2, "__flash1", 1, ".progmem1.data" },
{ ADDR_SPACE_FLASH2, 1, 2, "__flash2", 2, ".progmem2.data" },
{ ADDR_SPACE_FLASH3, 1, 2, "__flash3", 3, ".progmem3.data" },
{ ADDR_SPACE_FLASH4, 1, 2, "__flash4", 4, ".progmem4.data" },
{ ADDR_SPACE_FLASH5, 1, 2, "__flash5", 5, ".progmem5.data" },
{ ADDR_SPACE_MEMX, 1, 3, "__memx", 0, ".progmemx.data" },
};
/* Holding RAM addresses of some SFRs used by the compiler and that
are unique over all devices in an architecture like 'avr4'. */
typedef struct
{
/* SREG: The processor status */
int sreg;
/* RAMPX, RAMPY, RAMPD and CCP of XMEGA */
int ccp;
int rampd;
int rampx;
int rampy;
/* RAMPZ: The high byte of 24-bit address used with ELPM */
int rampz;
/* SP: The stack pointer and its low and high byte */
int sp_l;
int sp_h;
} avr_addr_t;
static avr_addr_t avr_addr;
/* Prototypes for local helper functions. */
static const char* out_movqi_r_mr (rtx_insn *, rtx[], int*);
static const char* out_movhi_r_mr (rtx_insn *, rtx[], int*);
static const char* out_movsi_r_mr (rtx_insn *, rtx[], int*);
static const char* out_movqi_mr_r (rtx_insn *, rtx[], int*);
static const char* out_movhi_mr_r (rtx_insn *, rtx[], int*);
static const char* out_movsi_mr_r (rtx_insn *, rtx[], int*);
static int get_sequence_length (rtx_insn *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 (machine_mode, const_tree);
static int avr_operand_rtx_cost (rtx, machine_mode, enum rtx_code,
int, bool);
static void output_reload_in_const (rtx*, rtx, int*, bool);
static struct machine_function * avr_init_machine_status (void);
/* Prototypes for hook implementors if needed before their implementation. */
static bool avr_rtx_costs (rtx, int, int, int, int*, bool);
/* Allocate registers from r25 to r8 for parameters for function calls. */
#define FIRST_CUM_REG 26
/* Last call saved register */
#define LAST_CALLEE_SAVED_REG (AVR_TINY ? 19 : 17)
/* Implicit target register of LPM instruction (R0) */
extern GTY(()) rtx lpm_reg_rtx;
rtx lpm_reg_rtx;
/* (Implicit) address register of LPM instruction (R31:R30 = Z) */
extern GTY(()) rtx lpm_addr_reg_rtx;
rtx lpm_addr_reg_rtx;
/* Temporary register RTX (reg:QI TMP_REGNO) */
extern GTY(()) rtx tmp_reg_rtx;
rtx tmp_reg_rtx;
/* Zeroed register RTX (reg:QI ZERO_REGNO) */
extern GTY(()) rtx zero_reg_rtx;
rtx zero_reg_rtx;
/* RTXs for all general purpose registers as QImode */
extern GTY(()) rtx all_regs_rtx[32];
rtx all_regs_rtx[32];
/* SREG, the processor status */
extern GTY(()) rtx sreg_rtx;
rtx sreg_rtx;
/* RAMP* special function registers */
extern GTY(()) rtx rampd_rtx;
extern GTY(()) rtx rampx_rtx;
extern GTY(()) rtx rampy_rtx;
extern GTY(()) rtx rampz_rtx;
rtx rampd_rtx;
rtx rampx_rtx;
rtx rampy_rtx;
rtx rampz_rtx;
/* RTX containing the strings "" and "e", respectively */
static GTY(()) rtx xstring_empty;
static GTY(()) rtx xstring_e;
/* Current architecture. */
const avr_arch_t *avr_arch;
/* Section to put switch tables in. */
static GTY(()) section *progmem_swtable_section;
/* Unnamed sections associated to __attribute__((progmem)) aka. PROGMEM
or to address space __flash* or __memx. Only used as singletons inside
avr_asm_select_section, but it must not be local there because of GTY. */
static GTY(()) section *progmem_section[ADDR_SPACE_COUNT];
/* Condition for insns/expanders from avr-dimode.md. */
bool avr_have_dimode = true;
/* To track if code will use .bss and/or .data. */
bool avr_need_clear_bss_p = false;
bool avr_need_copy_data_p = false;
/* Transform UP into lowercase and write the result to LO.
You must provide enough space for LO. Return LO. */
static char*
avr_tolower (char *lo, const char *up)
{
char *lo0 = lo;
for (; *up; up++, lo++)
*lo = TOLOWER (*up);
*lo = '\0';
return lo0;
}
/* Custom function to count number of set bits. */
static inline int
avr_popcount (unsigned int val)
{
int pop = 0;
while (val)
{
val &= val-1;
pop++;
}
return pop;
}
/* Constraint helper function. XVAL is a CONST_INT or a CONST_DOUBLE.
Return true if the least significant N_BYTES bytes of XVAL all have a
popcount in POP_MASK and false, otherwise. POP_MASK represents a subset
of integers which contains an integer N iff bit N of POP_MASK is set. */
bool
avr_popcount_each_byte (rtx xval, int n_bytes, int pop_mask)
{
int i;
machine_mode mode = GET_MODE (xval);
if (VOIDmode == mode)
mode = SImode;
for (i = 0; i < n_bytes; i++)
{
rtx xval8 = simplify_gen_subreg (QImode, xval, mode, i);
unsigned int val8 = UINTVAL (xval8) & GET_MODE_MASK (QImode);
if (0 == (pop_mask & (1 << avr_popcount (val8))))
return false;
}
return true;
}
/* Access some RTX as INT_MODE. If X is a CONST_FIXED we can get
the bit representation of X by "casting" it to CONST_INT. */
rtx
avr_to_int_mode (rtx x)
{
machine_mode mode = GET_MODE (x);
return VOIDmode == mode
? x
: simplify_gen_subreg (int_mode_for_mode (mode), x, mode, 0);
}
static const pass_data avr_pass_data_recompute_notes =
{
RTL_PASS, // type
"", // name (will be patched)
OPTGROUP_NONE, // optinfo_flags
TV_DF_SCAN, // tv_id
0, // properties_required
0, // properties_provided
0, // properties_destroyed
0, // todo_flags_start
TODO_df_finish | TODO_df_verify // todo_flags_finish
};
class avr_pass_recompute_notes : public rtl_opt_pass
{
public:
avr_pass_recompute_notes (gcc::context *ctxt, const char *name)
: rtl_opt_pass (avr_pass_data_recompute_notes, ctxt)
{
this->name = name;
}
virtual unsigned int execute (function*)
{
df_note_add_problem ();
df_analyze ();
return 0;
}
}; // avr_pass_recompute_notes
static void
avr_register_passes (void)
{
/* This avr-specific pass (re)computes insn notes, in particular REG_DEAD
notes which are used by `avr.c::reg_unused_after' and branch offset
computations. These notes must be correct, i.e. there must be no
dangling REG_DEAD notes; otherwise wrong code might result, cf. PR64331.
DF needs (correct) CFG, hence right before free_cfg is the last
opportunity to rectify notes. */
register_pass (new avr_pass_recompute_notes (g, "avr-notes-free-cfg"),
PASS_POS_INSERT_BEFORE, "*free_cfg", 1);
}
/* Set `avr_arch' as specified by `-mmcu='.
Return true on success. */
static bool
avr_set_core_architecture (void)
{
/* Search for mcu core architecture. */
if (!avr_mmcu)
avr_mmcu = AVR_MMCU_DEFAULT;
avr_arch = &avr_arch_types[0];
for (const avr_mcu_t *mcu = avr_mcu_types; ; mcu++)
{
if (NULL == mcu->name)
{
/* Reached the end of `avr_mcu_types'. This should actually never
happen as options are provided by device-specs. It could be a
typo in a device-specs or calling the compiler proper directly
with -mmcu=<device>. */
error ("unknown core architecture %qs specified with %qs",
avr_mmcu, "-mmcu=");
avr_inform_core_architectures ();
break;
}
else if (0 == strcmp (mcu->name, avr_mmcu)
// Is this a proper architecture ?
&& NULL == mcu->macro)
{
avr_arch = &avr_arch_types[mcu->arch_id];
if (avr_n_flash < 0)
avr_n_flash = mcu->n_flash;
return true;
}
}
return false;
}
/* Implement `TARGET_OPTION_OVERRIDE'. */
static void
avr_option_override (void)
{
/* Disable -fdelete-null-pointer-checks option for AVR target.
This option compiler assumes that dereferencing of a null pointer
would halt the program. For AVR this assumption is not true and
programs can safely dereference null pointers. Changes made by this
option may not work properly for AVR. So disable this option. */
flag_delete_null_pointer_checks = 0;
/* caller-save.c looks for call-clobbered hard registers that are assigned
to pseudos that cross calls and tries so save-restore them around calls
in order to reduce the number of stack slots needed.
This might lead to situations where reload is no more able to cope
with the challenge of AVR's very few address registers and fails to
perform the requested spills. */
if (avr_strict_X)
flag_caller_saves = 0;
/* Unwind tables currently require a frame pointer for correctness,
see toplev.c:process_options(). */
if ((flag_unwind_tables
|| flag_non_call_exceptions
|| flag_asynchronous_unwind_tables)
&& !ACCUMULATE_OUTGOING_ARGS)
{
flag_omit_frame_pointer = 0;
}
if (flag_pic == 1)
warning (OPT_fpic, "-fpic is not supported");
if (flag_pic == 2)
warning (OPT_fPIC, "-fPIC is not supported");
if (flag_pie == 1)
warning (OPT_fpie, "-fpie is not supported");
if (flag_pie == 2)
warning (OPT_fPIE, "-fPIE is not supported");
if (!avr_set_core_architecture())
return;
/* RAM addresses of some SFRs common to all devices in respective arch. */
/* SREG: Status Register containing flags like I (global IRQ) */
avr_addr.sreg = 0x3F + avr_arch->sfr_offset;
/* RAMPZ: Address' high part when loading via ELPM */
avr_addr.rampz = 0x3B + avr_arch->sfr_offset;
avr_addr.rampy = 0x3A + avr_arch->sfr_offset;
avr_addr.rampx = 0x39 + avr_arch->sfr_offset;
avr_addr.rampd = 0x38 + avr_arch->sfr_offset;
avr_addr.ccp = (AVR_TINY ? 0x3C : 0x34) + avr_arch->sfr_offset;
/* SP: Stack Pointer (SP_H:SP_L) */
avr_addr.sp_l = 0x3D + avr_arch->sfr_offset;
avr_addr.sp_h = avr_addr.sp_l + 1;
init_machine_status = avr_init_machine_status;
avr_log_set_avr_log();
/* Register some avr-specific pass(es). There is no canonical place for
pass registration. This function is convenient. */
avr_register_passes ();
}
/* Function to set up the backend function structure. */
static struct machine_function *
avr_init_machine_status (void)
{
return ggc_cleared_alloc<machine_function> ();
}
/* Implement `INIT_EXPANDERS'. */
/* The function works like a singleton. */
void
avr_init_expanders (void)
{
int regno;
for (regno = 0; regno < 32; regno ++)
all_regs_rtx[regno] = gen_rtx_REG (QImode, regno);
lpm_reg_rtx = all_regs_rtx[LPM_REGNO];
tmp_reg_rtx = all_regs_rtx[AVR_TMP_REGNO];
zero_reg_rtx = all_regs_rtx[AVR_ZERO_REGNO];
lpm_addr_reg_rtx = gen_rtx_REG (HImode, REG_Z);
sreg_rtx = gen_rtx_MEM (QImode, GEN_INT (avr_addr.sreg));
rampd_rtx = gen_rtx_MEM (QImode, GEN_INT (avr_addr.rampd));
rampx_rtx = gen_rtx_MEM (QImode, GEN_INT (avr_addr.rampx));
rampy_rtx = gen_rtx_MEM (QImode, GEN_INT (avr_addr.rampy));
rampz_rtx = gen_rtx_MEM (QImode, GEN_INT (avr_addr.rampz));
xstring_empty = gen_rtx_CONST_STRING (VOIDmode, "");
xstring_e = gen_rtx_CONST_STRING (VOIDmode, "e");
/* TINY core does not have regs r10-r16, but avr-dimode.md expects them
to be present */
if (AVR_TINY)
avr_have_dimode = false;
}
/* Implement `REGNO_REG_CLASS'. */
/* Return register class for register R. */
enum reg_class
avr_regno_reg_class (int r)
{
static const enum reg_class reg_class_tab[] =
{
R0_REG,
/* r1 - r15 */
NO_LD_REGS, NO_LD_REGS, NO_LD_REGS,
NO_LD_REGS, NO_LD_REGS, NO_LD_REGS, NO_LD_REGS,
NO_LD_REGS, NO_LD_REGS, NO_LD_REGS, NO_LD_REGS,
NO_LD_REGS, NO_LD_REGS, NO_LD_REGS, NO_LD_REGS,
/* r16 - r23 */
SIMPLE_LD_REGS, SIMPLE_LD_REGS, SIMPLE_LD_REGS, SIMPLE_LD_REGS,
SIMPLE_LD_REGS, SIMPLE_LD_REGS, SIMPLE_LD_REGS, SIMPLE_LD_REGS,
/* r24, r25 */
ADDW_REGS, ADDW_REGS,
/* X: r26, 27 */
POINTER_X_REGS, POINTER_X_REGS,
/* Y: r28, r29 */
POINTER_Y_REGS, POINTER_Y_REGS,
/* Z: r30, r31 */
POINTER_Z_REGS, POINTER_Z_REGS,
/* SP: SPL, SPH */
STACK_REG, STACK_REG
};
if (r <= 33)
return reg_class_tab[r];
return ALL_REGS;
}
/* Implement `TARGET_SCALAR_MODE_SUPPORTED_P'. */
static bool
avr_scalar_mode_supported_p (machine_mode mode)
{
if (ALL_FIXED_POINT_MODE_P (mode))
return true;
if (PSImode == mode)
return true;
return default_scalar_mode_supported_p (mode);
}
/* Return TRUE if DECL is a VAR_DECL located in flash and FALSE, otherwise. */
static bool
avr_decl_flash_p (tree decl)
{
if (TREE_CODE (decl) != VAR_DECL
|| TREE_TYPE (decl) == error_mark_node)
{
return false;
}
return !ADDR_SPACE_GENERIC_P (TYPE_ADDR_SPACE (TREE_TYPE (decl)));
}
/* Return TRUE if DECL is a VAR_DECL located in the 24-bit flash
address space and FALSE, otherwise. */
static bool
avr_decl_memx_p (tree decl)
{
if (TREE_CODE (decl) != VAR_DECL
|| TREE_TYPE (decl) == error_mark_node)
{
return false;
}
return (ADDR_SPACE_MEMX == TYPE_ADDR_SPACE (TREE_TYPE (decl)));
}
/* Return TRUE if X is a MEM rtx located in flash and FALSE, otherwise. */
bool
avr_mem_flash_p (rtx x)
{
return (MEM_P (x)
&& !ADDR_SPACE_GENERIC_P (MEM_ADDR_SPACE (x)));
}
/* Return TRUE if X is a MEM rtx located in the 24-bit flash
address space and FALSE, otherwise. */
bool
avr_mem_memx_p (rtx x)
{
return (MEM_P (x)
&& ADDR_SPACE_MEMX == MEM_ADDR_SPACE (x));
}
/* 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)
{
return avr_lookup_function_attribute1 (func, "naked");
}
/* Return nonzero if FUNC is an interrupt function as specified
by the "interrupt" attribute. */
static int
avr_interrupt_function_p (tree func)
{
return avr_lookup_function_attribute1 (func, "interrupt");
}
/* Return nonzero if FUNC is a signal function as specified
by the "signal" attribute. */
static int
avr_signal_function_p (tree func)
{
return avr_lookup_function_attribute1 (func, "signal");
}
/* Return nonzero if FUNC is an OS_task function. */
static int
avr_OS_task_function_p (tree func)
{
return avr_lookup_function_attribute1 (func, "OS_task");
}
/* Return nonzero if FUNC is an OS_main function. */
static int
avr_OS_main_function_p (tree func)
{
return avr_lookup_function_attribute1 (func, "OS_main");
}
/* Implement `TARGET_SET_CURRENT_FUNCTION'. */
/* Sanity cheching for above function attributes. */
static void
avr_set_current_function (tree decl)
{
location_t loc;
const char *isr;
if (decl == NULL_TREE
|| current_function_decl == NULL_TREE
|| current_function_decl == error_mark_node
|| ! cfun->machine
|| cfun->machine->attributes_checked_p)
return;
loc = DECL_SOURCE_LOCATION (decl);
cfun->machine->is_naked = avr_naked_function_p (decl);
cfun->machine->is_signal = avr_signal_function_p (decl);
cfun->machine->is_interrupt = avr_interrupt_function_p (decl);
cfun->machine->is_OS_task = avr_OS_task_function_p (decl);
cfun->machine->is_OS_main = avr_OS_main_function_p (decl);
isr = cfun->machine->is_interrupt ? "interrupt" : "signal";
/* Too much attributes make no sense as they request conflicting features. */
if (cfun->machine->is_OS_task + cfun->machine->is_OS_main
+ (cfun->machine->is_signal || cfun->machine->is_interrupt) > 1)
error_at (loc, "function attributes %qs, %qs and %qs are mutually"
" exclusive", "OS_task", "OS_main", isr);
/* 'naked' will hide effects of 'OS_task' and 'OS_main'. */
if (cfun->machine->is_naked
&& (cfun->machine->is_OS_task || cfun->machine->is_OS_main))
warning_at (loc, OPT_Wattributes, "function attributes %qs and %qs have"
" no effect on %qs function", "OS_task", "OS_main", "naked");
if (cfun->machine->is_interrupt || cfun->machine->is_signal)
{
tree args = TYPE_ARG_TYPES (TREE_TYPE (decl));
tree ret = TREE_TYPE (TREE_TYPE (decl));
const char *name;
name = DECL_ASSEMBLER_NAME_SET_P (decl)
? IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (decl))
: IDENTIFIER_POINTER (DECL_NAME (decl));
/* Skip a leading '*' that might still prefix the assembler name,
e.g. in non-LTO runs. */
name = default_strip_name_encoding (name);
/* Interrupt handlers must be void __vector (void) functions. */
if (args && TREE_CODE (TREE_VALUE (args)) != VOID_TYPE)
error_at (loc, "%qs function cannot have arguments", isr);
if (TREE_CODE (ret) != VOID_TYPE)
error_at (loc, "%qs function cannot return a value", isr);
#if defined WITH_AVRLIBC
/* Silently ignore 'signal' if 'interrupt' is present. AVR-LibC startet
using this when it switched from SIGNAL and INTERRUPT to ISR. */
if (cfun->machine->is_interrupt)
cfun->machine->is_signal = 0;
/* If the function has the 'signal' or 'interrupt' attribute, ensure
that the name of the function is "__vector_NN" so as to catch
when the user misspells the vector name. */
if (!STR_PREFIX_P (name, "__vector"))
warning_at (loc, OPT_Wmisspelled_isr, "%qs appears to be a misspelled "
"%qs handler, missing %<__vector%> prefix", name, isr);
#endif // AVR-LibC naming conventions
}
#if defined WITH_AVRLIBC
// Common problem is using "ISR" without first including avr/interrupt.h.
const char *name = IDENTIFIER_POINTER (DECL_NAME (decl));
name = default_strip_name_encoding (name);
if (0 == strcmp ("ISR", name)
|| 0 == strcmp ("INTERRUPT", name)
|| 0 == strcmp ("SIGNAL", name))
{
warning_at (loc, OPT_Wmisspelled_isr, "%qs is a reserved identifier"
" in AVR-LibC. Consider %<#include <avr/interrupt.h>%>"
" before using the %qs macro", name, name);
}
#endif // AVR-LibC naming conventions
/* Don't print the above diagnostics more than once. */
cfun->machine->attributes_checked_p = 1;
}
/* Implement `ACCUMULATE_OUTGOING_ARGS'. */
int
avr_accumulate_outgoing_args (void)
{
if (!cfun)
return TARGET_ACCUMULATE_OUTGOING_ARGS;
/* FIXME: For setjmp and in avr_builtin_setjmp_frame_value we don't know
what offset is correct. In some cases it is relative to
virtual_outgoing_args_rtx and in others it is relative to
virtual_stack_vars_rtx. For example code see
gcc.c-torture/execute/built-in-setjmp.c
gcc.c-torture/execute/builtins/sprintf-chk.c */
return (TARGET_ACCUMULATE_OUTGOING_ARGS
&& !(cfun->calls_setjmp
|| cfun->has_nonlocal_label));
}
/* Report contribution of accumulated outgoing arguments to stack size. */
static inline int
avr_outgoing_args_size (void)
{
return ACCUMULATE_OUTGOING_ARGS ? crtl->outgoing_args_size : 0;
}
/* Implement `STARTING_FRAME_OFFSET'. */
/* This is the offset from the frame pointer register to the first stack slot
that contains a variable living in the frame. */
int
avr_starting_frame_offset (void)
{
return 1 + avr_outgoing_args_size ();
}
/* 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 = cfun->machine->is_interrupt || cfun->machine->is_signal;
if (set)
CLEAR_HARD_REG_SET (*set);
count = 0;
/* No need to save any registers if the function never returns or
has the "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 && !crtl->is_leaf && call_used_regs[reg])
|| (df_regs_ever_live_p (reg)
&& (int_or_sig_p || !call_used_regs[reg])
/* Don't record frame pointer registers here. They are treated
indivitually in prologue. */
&& !(frame_pointer_needed
&& (reg == REG_Y || reg == (REG_Y+1)))))
{
if (set)
SET_HARD_REG_BIT (*set, reg);
count++;
}
}
return count;
}
/* Implement `TARGET_ALLOCATE_STACK_SLOTS_FOR_ARGS' */
static bool
avr_allocate_stack_slots_for_args (void)
{
return !cfun->machine->is_naked;
}
/* Return true if register FROM can be eliminated via register TO. */
static bool
avr_can_eliminate (const int from ATTRIBUTE_UNUSED, const int to)
{
return ((frame_pointer_needed && to == FRAME_POINTER_REGNUM)
|| !frame_pointer_needed);
}
/* Implement `TARGET_WARN_FUNC_RETURN'. */
static bool
avr_warn_func_return (tree decl)
{
/* Naked functions are implemented entirely in assembly, including the
return sequence, so suppress warnings about this. */
return !avr_naked_function_p (decl);
}
/* 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_outgoing_args_size()
+ avr_pc_size + 1 + offset);
}
}
/* Helper for the function below. */
static void
avr_adjust_type_node (tree *node, machine_mode mode, int sat_p)
{
*node = make_node (FIXED_POINT_TYPE);
TYPE_SATURATING (*node) = sat_p;
TYPE_UNSIGNED (*node) = UNSIGNED_FIXED_POINT_MODE_P (mode);
TYPE_IBIT (*node) = GET_MODE_IBIT (mode);
TYPE_FBIT (*node) = GET_MODE_FBIT (mode);
TYPE_PRECISION (*node) = GET_MODE_BITSIZE (mode);
TYPE_ALIGN (*node) = 8;
SET_TYPE_MODE (*node, mode);
layout_type (*node);
}
/* Implement `TARGET_BUILD_BUILTIN_VA_LIST'. */
static tree
avr_build_builtin_va_list (void)
{
/* avr-modes.def adjusts [U]TA to be 64-bit modes with 48 fractional bits.
This is more appropriate for the 8-bit machine AVR than 128-bit modes.
The ADJUST_IBIT/FBIT are handled in toplev:init_adjust_machine_modes()
which is auto-generated by genmodes, but the compiler assigns [U]DAmode
to the long long accum modes instead of the desired [U]TAmode.
Fix this now, right after node setup in tree.c:build_common_tree_nodes().
This must run before c-cppbuiltin.c:builtin_define_fixed_point_constants()
which built-in defines macros like __ULLACCUM_FBIT__ that are used by
libgcc to detect IBIT and FBIT. */
avr_adjust_type_node (&ta_type_node, TAmode, 0);
avr_adjust_type_node (&uta_type_node, UTAmode, 0);
avr_adjust_type_node (&sat_ta_type_node, TAmode, 1);
avr_adjust_type_node (&sat_uta_type_node, UTAmode, 1);
unsigned_long_long_accum_type_node = uta_type_node;
long_long_accum_type_node = ta_type_node;
sat_unsigned_long_long_accum_type_node = sat_uta_type_node;
sat_long_long_accum_type_node = sat_ta_type_node;
/* Dispatch to the default handler. */
return std_build_builtin_va_list ();
}
/* Implement `TARGET_BUILTIN_SETJMP_FRAME_VALUE'. */
/* 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. */
static rtx
avr_builtin_setjmp_frame_value (void)
{
rtx xval = gen_reg_rtx (Pmode);
emit_insn (gen_subhi3 (xval, virtual_stack_vars_rtx,
gen_int_mode (STARTING_FRAME_OFFSET, Pmode)));
return xval;
}
/* 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 function's 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_outgoing_args_size() == 0
&& avr_regs_to_save (NULL) == 0
&& ! cfun->machine->is_interrupt
&& ! cfun->machine->is_signal
&& ! cfun->machine->is_naked
&& ! 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 <= LAST_CALLEE_SAVED_REG; ++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_insn *insns)
{
rtx_insn *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 (Pmode, 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;
rtx_insn *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++;
}
/* Helper for expand_prologue. Emit a push of a SFR via tmp_reg.
SFR is a MEM representing the memory location of the SFR.
If CLR_P then clear the SFR after the push using zero_reg. */
static void
emit_push_sfr (rtx sfr, bool frame_related_p, bool clr_p)
{
rtx_insn *insn;
gcc_assert (MEM_P (sfr));
/* IN __tmp_reg__, IO(SFR) */
insn = emit_move_insn (tmp_reg_rtx, sfr);
if (frame_related_p)
RTX_FRAME_RELATED_P (insn) = 1;
/* PUSH __tmp_reg__ */
emit_push_byte (AVR_TMP_REGNO, frame_related_p);
if (clr_p)
{
/* OUT IO(SFR), __zero_reg__ */
insn = emit_move_insn (sfr, const0_rtx);
if (frame_related_p)
RTX_FRAME_RELATED_P (insn) = 1;
}
}
static void
avr_prologue_setup_frame (HOST_WIDE_INT size, HARD_REG_SET set)
{
rtx_insn *insn;
bool isr_p = cfun->machine->is_interrupt || cfun->machine->is_signal;
int live_seq = sequent_regs_live ();
HOST_WIDE_INT size_max
= (HOST_WIDE_INT) GET_MODE_MASK (AVR_HAVE_8BIT_SP ? QImode : Pmode);
bool minimize = (TARGET_CALL_PROLOGUES
&& size < size_max
&& live_seq
&& !isr_p
&& !cfun->machine->is_OS_task
&& !cfun->machine->is_OS_main
&& !AVR_TINY);
if (minimize
&& (frame_pointer_needed
|| avr_outgoing_args_size() > 8
|| (AVR_2_BYTE_PC && live_seq > 6)
|| live_seq > 7))
{
rtx pattern;
int first_reg, reg, offset;
emit_move_insn (gen_rtx_REG (HImode, REG_X),
gen_int_mode (size, HImode));
pattern = gen_call_prologue_saves (gen_int_mode (live_seq, HImode),
gen_int_mode (live_seq+size, HImode));
insn = emit_insn (pattern);
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 (Pmode, 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 = (LAST_CALLEE_SAVED_REG + 1) - (live_seq - 2);
for (reg = 29, offset = -live_seq + 1;
reg >= first_reg;
reg = (reg == 28 ? LAST_CALLEE_SAVED_REG : reg - 1), ++offset)
{
rtx m, r;
m = gen_rtx_MEM (QImode, plus_constant (Pmode, 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 /* !minimize */
{
int reg;
for (reg = 0; reg < 32; ++reg)
if (TEST_HARD_REG_BIT (set, reg))
emit_push_byte (reg, true);
if (frame_pointer_needed
&& (!(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 (frame_pointer_needed
&& size == 0)
{
insn = emit_move_insn (frame_pointer_rtx, stack_pointer_rtx);
RTX_FRAME_RELATED_P (insn) = 1;
}
if (size != 0)
{
/* 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.
There is also the case where SIZE != 0 and no frame pointer is
needed; this can occur if ACCUMULATE_OUTGOING_ARGS is on.
In that case, insn (*) is not needed in that case.
We use the X register as scratch. This is save because in X
is call-clobbered.
In an interrupt routine, the case of SIZE != 0 together with
!frame_pointer_needed can only occur if the function is not a
leaf function and thus X has already been saved. */
int irq_state = -1;
HOST_WIDE_INT size_cfa = size, neg_size;
rtx_insn *fp_plus_insns;
rtx fp, my_fp;
gcc_assert (frame_pointer_needed
|| !isr_p
|| !crtl->is_leaf);
fp = my_fp = (frame_pointer_needed
? frame_pointer_rtx
: gen_rtx_REG (Pmode, REG_X));
if (AVR_HAVE_8BIT_SP)
{
/* The high byte (r29) does not change:
Prefer SUBI (1 cycle) over SBIW (2 cycles, same size). */
my_fp = all_regs_rtx[FRAME_POINTER_REGNUM];
}
/* Cut down size and avoid size = 0 so that we don't run
into ICE like PR52488 in the remainder. */
if (size > size_max)
{
/* Don't error so that insane code from newlib still compiles
and does not break building newlib. As PR51345 is implemented
now, there are multilib variants with -msp8.
If user wants sanity checks he can use -Wstack-usage=
or similar options.
For CFA we emit the original, non-saturated size so that
the generic machinery is aware of the real stack usage and
will print the above diagnostic as expected. */
size = size_max;
}
size = trunc_int_for_mode (size, GET_MODE (my_fp));
neg_size = trunc_int_for_mode (-size, GET_MODE (my_fp));
/************ 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. */
insn = emit_move_insn (fp, stack_pointer_rtx);
if (frame_pointer_needed)
{
RTX_FRAME_RELATED_P (insn) = 1;
add_reg_note (insn, REG_CFA_ADJUST_CFA,
gen_rtx_SET (VOIDmode, fp, stack_pointer_rtx));
}
insn = emit_move_insn (my_fp, plus_constant (GET_MODE (my_fp),
my_fp, neg_size));
if (frame_pointer_needed)
{
RTX_FRAME_RELATED_P (insn) = 1;
add_reg_note (insn, REG_CFA_ADJUST_CFA,
gen_rtx_SET (VOIDmode, fp,
plus_constant (Pmode, fp,
-size_cfa)));
}
/* Copy to stack pointer. Note that since we've already
changed the CFA to the frame pointer this operation
need not be annotated if frame pointer is needed.
Always move through unspec, see PR50063.
For meaning of irq_state see movhi_sp_r insn. */
if (cfun->machine->is_interrupt)
irq_state = 1;
if (TARGET_NO_INTERRUPTS
|| cfun->machine->is_signal
|| cfun->machine->is_OS_main)
irq_state = 0;
if (AVR_HAVE_8BIT_SP)
irq_state = 2;
insn = emit_insn (gen_movhi_sp_r (stack_pointer_rtx,
fp, GEN_INT (irq_state)));
if (!frame_pointer_needed)
{
RTX_FRAME_RELATED_P (insn) = 1;
add_reg_note (insn, REG_CFA_ADJUST_CFA,
gen_rtx_SET (VOIDmode, stack_pointer_rtx,
plus_constant (Pmode,
stack_pointer_rtx,
-size_cfa)));
}
fp_plus_insns = get_insns ();
end_sequence ();
/************ Method 2: Adjust Stack pointer ************/
/* Stack adjustment by means of RCALL . and/or PUSH __TMP_REG__
can only handle specific offsets. */
if (avr_sp_immediate_operand (gen_int_mode (-size, HImode), HImode))
{
rtx_insn *sp_plus_insns;
start_sequence ();
insn = emit_move_insn (stack_pointer_rtx,
plus_constant (Pmode, stack_pointer_rtx,
-size));
RTX_FRAME_RELATED_P (insn) = 1;
add_reg_note (insn, REG_CFA_ADJUST_CFA,
gen_rtx_SET (VOIDmode, stack_pointer_rtx,
plus_constant (Pmode,
stack_pointer_rtx,
-size_cfa)));
if (frame_pointer_needed)
{
insn = emit_move_insn (fp, stack_pointer_rtx);
RTX_FRAME_RELATED_P (insn) = 1;
}
sp_plus_insns = get_insns ();
end_sequence ();
/************ Use shortest method ************/
emit_insn (get_sequence_length (sp_plus_insns)
< get_sequence_length (fp_plus_insns)
? sp_plus_insns
: fp_plus_insns);
}
else
{
emit_insn (fp_plus_insns);
}
cfun->machine->stack_usage += size_cfa;
} /* !minimize && size != 0 */
} /* !minimize */
}
/* Output function prologue. */
void
avr_expand_prologue (void)
{
HARD_REG_SET set;
HOST_WIDE_INT size;
size = get_frame_size() + avr_outgoing_args_size();
cfun->machine->stack_usage = 0;
/* Prologue: naked. */
if (cfun->machine->is_naked)
{
return;
}
avr_regs_to_save (&set);
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 (AVR_ZERO_REGNO, true);
/* Push tmp reg. */
emit_push_byte (AVR_TMP_REGNO, true);
/* Push SREG. */
/* ??? There's no dwarf2 column reserved for SREG. */
emit_push_sfr (sreg_rtx, false, false /* clr */);
/* 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);
/* Push and clear RAMPD/X/Y/Z if present and low-part register is used.
??? There are no dwarf2 columns reserved for RAMPD/X/Y/Z. */
if (AVR_HAVE_RAMPD)
emit_push_sfr (rampd_rtx, false /* frame-related */, true /* clr */);
if (AVR_HAVE_RAMPX
&& TEST_HARD_REG_BIT (set, REG_X)
&& TEST_HARD_REG_BIT (set, REG_X + 1))
{
emit_push_sfr (rampx_rtx, false /* frame-related */, true /* clr */);
}
if (AVR_HAVE_RAMPY
&& (frame_pointer_needed
|| (TEST_HARD_REG_BIT (set, REG_Y)
&& TEST_HARD_REG_BIT (set, REG_Y + 1))))
{
emit_push_sfr (rampy_rtx, false /* frame-related */, true /* clr */);
}
if (AVR_HAVE_RAMPZ
&& TEST_HARD_REG_BIT (set, REG_Z)
&& TEST_HARD_REG_BIT (set, REG_Z + 1))
{
emit_push_sfr (rampz_rtx, false /* frame-related */, AVR_HAVE_RAMPD);
}
} /* is_interrupt is_signal */
avr_prologue_setup_frame (size, set);
if (flag_stack_usage_info)
current_function_static_stack_size = cfun->machine->stack_usage;
}
/* Implement `TARGET_ASM_FUNCTION_END_PROLOGUE'. */
/* 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);
}
if (ACCUMULATE_OUTGOING_ARGS)
fprintf (file, "/* outgoing args size = %d */\n",
avr_outgoing_args_size());
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 avr_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
avr_expand_epilogue (bool sibcall_p)
{
int reg;
int live_seq;
HARD_REG_SET set;
int minimize;
HOST_WIDE_INT size;
bool isr_p = cfun->machine->is_interrupt || cfun->machine->is_signal;
size = get_frame_size() + avr_outgoing_args_size();
/* epilogue: naked */
if (cfun->machine->is_naked)
{
gcc_assert (!sibcall_p);
emit_jump_insn (gen_return ());
return;
}
avr_regs_to_save (&set);
live_seq = sequent_regs_live ();
minimize = (TARGET_CALL_PROLOGUES
&& live_seq
&& !isr_p
&& !cfun->machine->is_OS_task
&& !cfun->machine->is_OS_main
&& !AVR_TINY);
if (minimize
&& (live_seq > 4
|| frame_pointer_needed
|| size))
{
/* Get rid of frame. */
if (!frame_pointer_needed)
{
emit_move_insn (frame_pointer_rtx, stack_pointer_rtx);
}
if (size)
{
emit_move_insn (frame_pointer_rtx,
plus_constant (Pmode, frame_pointer_rtx, size));
}
emit_insn (gen_epilogue_restores (gen_int_mode (live_seq, HImode)));
return;
}
if (size)
{
/* Try two methods to adjust stack and select shortest. */
int irq_state = -1;
rtx fp, my_fp;
rtx_insn *fp_plus_insns;
HOST_WIDE_INT size_max;
gcc_assert (frame_pointer_needed
|| !isr_p
|| !crtl->is_leaf);
fp = my_fp = (frame_pointer_needed
? frame_pointer_rtx
: gen_rtx_REG (Pmode, REG_X));
if (AVR_HAVE_8BIT_SP)
{
/* The high byte (r29) does not change:
Prefer SUBI (1 cycle) over SBIW (2 cycles). */
my_fp = all_regs_rtx[FRAME_POINTER_REGNUM];
}
/* For rationale see comment in prologue generation. */
size_max = (HOST_WIDE_INT) GET_MODE_MASK (GET_MODE (my_fp));
if (size > size_max)
size = size_max;
size = trunc_int_for_mode (size, GET_MODE (my_fp));
/********** Method 1: Adjust fp register **********/
start_sequence ();
if (!frame_pointer_needed)
emit_move_insn (fp, stack_pointer_rtx);
emit_move_insn (my_fp, plus_constant (GET_MODE (my_fp), my_fp, size));
/* Copy to stack pointer. */
if (TARGET_NO_INTERRUPTS)
irq_state = 0;
if (AVR_HAVE_8BIT_SP)
irq_state = 2;
emit_insn (gen_movhi_sp_r (stack_pointer_rtx, fp,
GEN_INT (irq_state)));
fp_plus_insns = get_insns ();
end_sequence ();
/********** Method 2: Adjust Stack pointer **********/
if (avr_sp_immediate_operand (gen_int_mode (size, HImode), HImode))
{
rtx_insn *sp_plus_insns;
start_sequence ();
emit_move_insn (stack_pointer_rtx,
plus_constant (Pmode, stack_pointer_rtx, size));
sp_plus_insns = get_insns ();
end_sequence ();
/************ Use shortest method ************/
emit_insn (get_sequence_length (sp_plus_insns)
< get_sequence_length (fp_plus_insns)
? sp_plus_insns
: fp_plus_insns);
}
else
emit_insn (fp_plus_insns);
} /* size != 0 */
if (frame_pointer_needed
&& !(cfun->machine->is_OS_task || cfun->machine->is_OS_main))
{
/* Restore previous frame_pointer. See avr_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 (isr_p)
{
/* Restore RAMPZ/Y/X/D using tmp_reg as scratch.
The conditions to restore them must be tha same as in prologue. */
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 (rampz_rtx, tmp_reg_rtx);
}
if (AVR_HAVE_RAMPY
&& (frame_pointer_needed
|| (TEST_HARD_REG_BIT (set, REG_Y)
&& TEST_HARD_REG_BIT (set, REG_Y + 1))))
{
emit_pop_byte (TMP_REGNO);
emit_move_insn (rampy_rtx, tmp_reg_rtx);
}
if (AVR_HAVE_RAMPX
&& TEST_HARD_REG_BIT (set, REG_X)
&& TEST_HARD_REG_BIT (set, REG_X + 1))
{
emit_pop_byte (TMP_REGNO);
emit_move_insn (rampx_rtx, tmp_reg_rtx);
}
if (AVR_HAVE_RAMPD)
{
emit_pop_byte (TMP_REGNO);
emit_move_insn (rampd_rtx, tmp_reg_rtx);
}
/* Restore SREG using tmp_reg as scratch. */
emit_pop_byte (AVR_TMP_REGNO);
emit_move_insn (sreg_rtx, tmp_reg_rtx);
/* Restore tmp REG. */
emit_pop_byte (AVR_TMP_REGNO);
/* Restore zero REG. */
emit_pop_byte (AVR_ZERO_REGNO);
}
if (!sibcall_p)
emit_jump_insn (gen_return ());
}
/* Implement `TARGET_ASM_FUNCTION_BEGIN_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;
}
/* Implement `TARGET_MODE_DEPENDENT_ADDRESS_P'. */
static bool
avr_mode_dependent_address_p (const_rtx addr ATTRIBUTE_UNUSED, addr_space_t as)
{
/* FIXME: Non-generic addresses are not mode-dependent in themselves.
This hook just serves to hack around PR rtl-optimization/52543 by
claiming that non-generic addresses were mode-dependent so that
lower-subreg.c will skip these addresses. lower-subreg.c sets up fake
RTXes to probe SET and MEM costs and assumes that MEM is always in the
generic address space which is not true. */
return !ADDR_SPACE_GENERIC_P (as);
}
/* Helper function for `avr_legitimate_address_p'. */
static inline bool
avr_reg_ok_for_addr_p (rtx reg, addr_space_t as,
RTX_CODE outer_code, bool strict)
{
return (REG_P (reg)
&& (avr_regno_mode_code_ok_for_base_p (REGNO (reg), QImode,
as, outer_code, UNKNOWN)
|| (!strict
&& REGNO (reg) >= FIRST_PSEUDO_REGISTER)));
}
/* Return nonzero if X (an RTX) is a legitimate memory address on the target
machine for a memory operand of mode MODE. */
static bool
avr_legitimate_address_p (machine_mode mode, rtx x, bool strict)
{
bool ok = CONSTANT_ADDRESS_P (x);
switch (GET_CODE (x))
{
case REG:
ok = avr_reg_ok_for_addr_p (x, ADDR_SPACE_GENERIC,
MEM, strict);
if (strict
&& GET_MODE_SIZE (mode) > 4
&& REG_X == REGNO (x))
{
ok = false;
}
break;
case POST_INC:
case PRE_DEC:
ok = avr_reg_ok_for_addr_p (XEXP (x, 0), ADDR_SPACE_GENERIC,
GET_CODE (x), strict);
break;
case PLUS:
{
rtx reg = XEXP (x, 0);
rtx op1 = XEXP (x, 1);
if (REG_P (reg)
&& CONST_INT_P (op1)
&& INTVAL (op1) >= 0)
{
bool fit = IN_RANGE (INTVAL (op1), 0, MAX_LD_OFFSET (mode));
if (fit)
{
ok = (! strict
|| avr_reg_ok_for_addr_p (reg, ADDR_SPACE_GENERIC,
PLUS, strict));
if (reg == frame_pointer_rtx
|| reg == arg_pointer_rtx)
{
ok = true;
}
}
else if (frame_pointer_needed
&& reg == frame_pointer_rtx)
{
ok = true;
}
}
}
break;
default:
break;
}
if (AVR_TINY
&& CONSTANT_ADDRESS_P (x))
{
/* avrtiny's load / store instructions only cover addresses 0..0xbf:
IN / OUT range is 0..0x3f and LDS / STS can access 0x40..0xbf. */
ok = (CONST_INT_P (x)
&& IN_RANGE (INTVAL (x), 0, 0xc0 - GET_MODE_SIZE (mode)));
}
if (avr_log.legitimate_address_p)
{
avr_edump ("\n%?: ret=%d, mode=%m strict=%d "
"reload_completed=%d reload_in_progress=%d %s:",
ok, mode, strict, reload_completed, reload_in_progress,
reg_renumber ? "(reg_renumber)" : "");
if (GET_CODE (x) == PLUS
&& REG_P (XEXP (x, 0))
&& CONST_INT_P (XEXP (x, 1))
&& IN_RANGE (INTVAL (XEXP (x, 1)), 0, MAX_LD_OFFSET (mode))
&& reg_renumber)
{
avr_edump ("(r%d ---> r%d)", REGNO (XEXP (x, 0)),
true_regnum (XEXP (x, 0)));
}
avr_edump ("\n%r\n", x);
}
return ok;
}
/* Former implementation of TARGET_LEGITIMIZE_ADDRESS,
now only a helper for avr_addr_space_legitimize_address. */
/* Attempts to replace X with a valid
memory address for an operand of mode MODE */
static rtx
avr_legitimize_address (rtx x, rtx oldx, machine_mode mode)
{
bool big_offset_p = false;
x = 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 (CONST_INT_P (XEXP (oldx, 1)))
{
int offs = INTVAL (XEXP (oldx, 1));
if (frame_pointer_rtx != XEXP (oldx, 0)
&& offs > MAX_LD_OFFSET (mode))
{
big_offset_p = true;
x = force_reg (GET_MODE (oldx), oldx);
}
}
}
if (avr_log.legitimize_address)
{
avr_edump ("\n%?: mode=%m\n %r\n", mode, oldx);
if (x != oldx)
avr_edump (" %s --> %r\n", big_offset_p ? "(big offset)" : "", x);
}
return x;
}
/* Implement `LEGITIMIZE_RELOAD_ADDRESS'. */
/* This will allow register R26/27 to be used where it is no worse than normal
base pointers R28/29 or R30/31. For example, if base offset is greater
than 63 bytes or for R++ or --R addressing. */
rtx
avr_legitimize_reload_address (rtx *px, machine_mode mode,
int opnum, int type, int addr_type,
int ind_levels ATTRIBUTE_UNUSED,
rtx (*mk_memloc)(rtx,int))
{
rtx x = *px;
if (avr_log.legitimize_reload_address)
avr_edump ("\n%?:%m %r\n", mode, x);
if (1 && (GET_CODE (x) == POST_INC
|| GET_CODE (x) == PRE_DEC))
{
push_reload (XEXP (x, 0), XEXP (x, 0), &XEXP (x, 0), &XEXP (x, 0),
POINTER_REGS, GET_MODE (x), GET_MODE (x), 0, 0,
opnum, RELOAD_OTHER);
if (avr_log.legitimize_reload_address)
avr_edump (" RCLASS.1 = %R\n IN = %r\n OUT = %r\n",
POINTER_REGS, XEXP (x, 0), XEXP (x, 0));
return x;
}
if (GET_CODE (x) == PLUS
&& REG_P (XEXP (x, 0))
&& 0 == reg_equiv_constant (REGNO (XEXP (x, 0)))
&& CONST_INT_P (XEXP (x, 1))
&& INTVAL (XEXP (x, 1)) >= 1)
{
bool fit = INTVAL (XEXP (x, 1)) <= MAX_LD_OFFSET (mode);
if (fit)
{
if (reg_equiv_address (REGNO (XEXP (x, 0))) != 0)
{
int regno = REGNO (XEXP (x, 0));
rtx mem = mk_memloc (x, regno);
push_reload (XEXP (mem, 0), NULL_RTX, &XEXP (mem, 0), NULL,
POINTER_REGS, Pmode, VOIDmode, 0, 0,
1, (enum reload_type) addr_type);
if (avr_log.legitimize_reload_address)
avr_edump (" RCLASS.2 = %R\n IN = %r\n OUT = %r\n",
POINTER_REGS, XEXP (mem, 0), NULL_RTX);
push_reload (mem, NULL_RTX, &XEXP (x, 0), NULL,
BASE_POINTER_REGS, GET_MODE (x), VOIDmode, 0, 0,
opnum, (enum reload_type) type);
if (avr_log.legitimize_reload_address)
avr_edump (" RCLASS.2 = %R\n IN = %r\n OUT = %r\n",
BASE_POINTER_REGS, mem, NULL_RTX);
return x;
}
}
else if (! (frame_pointer_needed
&& XEXP (x, 0) == frame_pointer_rtx))
{
push_reload (x, NULL_RTX, px, NULL,
POINTER_REGS, GET_MODE (x), VOIDmode, 0, 0,
opnum, (enum reload_type) type);
if (avr_log.legitimize_reload_address)
avr_edump (" RCLASS.3 = %R\n IN = %r\n OUT = %r\n",
POINTER_REGS, x, NULL_RTX);
return x;
}
}
return NULL_RTX;
}
/* Implement `TARGET_SECONDARY_RELOAD' */
static reg_class_t
avr_secondary_reload (bool in_p, rtx x,
reg_class_t reload_class ATTRIBUTE_UNUSED,
machine_mode mode, secondary_reload_info *sri)
{
if (in_p
&& MEM_P (x)
&& !ADDR_SPACE_GENERIC_P (MEM_ADDR_SPACE (x))
&& ADDR_SPACE_MEMX != MEM_ADDR_SPACE (x))
{
/* For the non-generic 16-bit spaces we need a d-class scratch. */
switch (mode)
{
default:
gcc_unreachable();
case QImode: sri->icode = CODE_FOR_reload_inqi; break;
case QQmode: sri->icode = CODE_FOR_reload_inqq; break;
case UQQmode: sri->icode = CODE_FOR_reload_inuqq; break;
case HImode: sri->icode = CODE_FOR_reload_inhi; break;
case HQmode: sri->icode = CODE_FOR_reload_inhq; break;
case HAmode: sri->icode = CODE_FOR_reload_inha; break;
case UHQmode: sri->icode = CODE_FOR_reload_inuhq; break;
case UHAmode: sri->icode = CODE_FOR_reload_inuha; break;
case PSImode: sri->icode = CODE_FOR_reload_inpsi; break;
case SImode: sri->icode = CODE_FOR_reload_insi; break;
case SFmode: sri->icode = CODE_FOR_reload_insf; break;
case SQmode: sri->icode = CODE_FOR_reload_insq; break;
case SAmode: sri->icode = CODE_FOR_reload_insa; break;
case USQmode: sri->icode = CODE_FOR_reload_inusq; break;
case USAmode: sri->icode = CODE_FOR_reload_inusa; break;
}
}
return NO_REGS;
}
/* Helper function to print assembler resp. track instruction
sequence lengths. Always return "".
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:
If N_WORDS >= 0 Add N_WORDS to *PLEN.
If N_WORDS < 0 Set *PLEN to -N_WORDS.
Don't output anything.
*/
static const char*
avr_asm_len (const char* tpl, rtx* operands, int* plen, int n_words)
{
if (NULL == plen)
{
output_asm_insn (tpl, operands);
}
else
{
if (n_words < 0)
*plen = -n_words;
else
*plen += n_words;
}
return "";
}
/* 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 ();
}
return "";
}
/* Implement `TARGET_PRINT_OPERAND_ADDRESS'. */
/* Output ADDR to FILE as address. */
static void
avr_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 = 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
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 smaller 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);
}
}
/* Implement `TARGET_PRINT_OPERAND_PUNCT_VALID_P'. */
static bool
avr_print_operand_punct_valid_p (unsigned char code)
{
return code == '~' || code == '!';
}
/* Implement `TARGET_PRINT_OPERAND'. */
/* Output X as assembler operand to file FILE.
For a description of supported %-codes, see top of avr.md. */
static void
avr_print_operand (FILE *file, rtx x, int code)
{
int abcd = 0, ef = 0, ij = 0;
if (code >= 'A' && code <= 'D')
abcd = code - 'A';
else if (code == 'E' || code == 'F')
ef = code - 'E';
else if (code == 'I' || code == 'J')
ij = code - 'I';
if (code == '~')
{
if (!AVR_HAVE_JMP_CALL)
fputc ('r', file);
}
else if (code == '!')
{
if (AVR_HAVE_EIJMP_EICALL)
fputc ('e', file);
}
else if (code == 't'
|| code == 'T')
{
static int t_regno = -1;
static int t_nbits = -1;
if (REG_P (x) && t_regno < 0 && code == 'T')
{
t_regno = REGNO (x);
t_nbits = GET_MODE_BITSIZE (GET_MODE (x));
}
else if (CONST_INT_P (x) && t_regno >= 0
&& IN_RANGE (INTVAL (x), 0, t_nbits - 1))
{
int bpos = INTVAL (x);
fprintf (file, "%s", reg_names[t_regno + bpos / 8]);
if (code == 'T')
fprintf (file, ",%d", bpos % 8);
t_regno = -1;
}
else
fatal_insn ("operands to %T/%t must be reg + const_int:", x);
}
else if (code == 'E' || code == 'F')
{
rtx op = XEXP(x, 0);
fprintf (file, reg_names[REGNO (op) + ef]);
}
else if (code == 'I' || code == 'J')
{
rtx op = XEXP(XEXP(x, 0), 0);
fprintf (file, reg_names[REGNO (op) + ij]);
}
else if (REG_P (x))
{
if (x == zero_reg_rtx)
fprintf (file, "__zero_reg__");
else if (code == 'r' && REGNO (x) < 32)
fprintf (file, "%d", (int) REGNO (x));
else
fprintf (file, reg_names[REGNO (x) + abcd]);
}
else if (CONST_INT_P (x))
{
HOST_WIDE_INT ival = INTVAL (x);
if ('i' != code)
fprintf (file, HOST_WIDE_INT_PRINT_DEC, ival + abcd);
else if (low_io_address_operand (x, VOIDmode)
|| high_io_address_operand (x, VOIDmode))
{
if (AVR_HAVE_RAMPZ && ival == avr_addr.rampz)
fprintf (file, "__RAMPZ__");
else if (AVR_HAVE_RAMPY && ival == avr_addr.rampy)
fprintf (file, "__RAMPY__");
else if (AVR_HAVE_RAMPX && ival == avr_addr.rampx)
fprintf (file, "__RAMPX__");
else if (AVR_HAVE_RAMPD && ival == avr_addr.rampd)
fprintf (file, "__RAMPD__");
else if ((AVR_XMEGA || AVR_TINY) && ival == avr_addr.ccp)
fprintf (file, "__CCP__");
else if (ival == avr_addr.sreg) fprintf (file, "__SREG__");
else if (ival == avr_addr.sp_l) fprintf (file, "__SP_L__");
else if (ival == avr_addr.sp_h) fprintf (file, "__SP_H__");
else
{
fprintf (file, HOST_WIDE_INT_PRINT_HEX,
ival - avr_arch->sfr_offset);
}
}
else
fatal_insn ("bad address, not an I/O address:", x);
}
else if (MEM_P (x))
{
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 == 'i')
{
avr_print_operand (file, addr, 'i');
}
else if (code == 'o')
{
if (GET_CODE (addr) != PLUS)
fatal_insn ("bad address, not (reg+disp):", addr);
avr_print_operand (file, XEXP (addr, 1), 0);
}
else if (code == 'b')
{
if (GET_CODE (addr) != PLUS)
fatal_insn ("bad address, not (reg+disp):", addr);
avr_print_operand_address (file, XEXP (addr, 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')
avr_print_operand_address (file, XEXP (addr, 0)); /* X, Y, Z */
else
avr_print_operand (file, XEXP (addr, 0), 0); /* r26, r28, r30 */
}
else if (GET_CODE (addr) == PLUS)
{
avr_print_operand_address (file, XEXP (addr,0));
if (REGNO (XEXP (addr, 0)) == REG_X)
fatal_insn ("internal compiler error. Bad address:"
,addr);
fputc ('+', file);
avr_print_operand (file, XEXP (addr,1), code);
}
else
avr_print_operand_address (file, addr);
}
else if (code == 'i')
{
if (GET_CODE (x) == SYMBOL_REF && (SYMBOL_REF_FLAGS (x) & SYMBOL_FLAG_IO))
avr_print_operand_address
(file, plus_constant (HImode, x, -avr_arch->sfr_offset));
else
fatal_insn ("bad address, not an I/O address:", x);
}
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 (CONST_FIXED_P (x))
{
HOST_WIDE_INT ival = INTVAL (avr_to_int_mode (x));
if (code != 0)
output_operand_lossage ("Unsupported code '%c' for fixed-point:",
code);
fprintf (file, HOST_WIDE_INT_PRINT_DEC, ival);
}
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 (GET_CODE (x) == CONST_STRING)
fputs (XSTR (x, 0), file);
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
avr_print_operand_address (file, x);
}
/* Worker function for `NOTICE_UPDATE_CC'. */
/* Update the condition code in the INSN. */
void
avr_notice_update_cc (rtx body ATTRIBUTE_UNUSED, rtx_insn *insn)
{
rtx set;
enum attr_cc cc = get_attr_cc (insn);
switch (cc)
{
default:
break;
case CC_PLUS:
case CC_LDI:
{
rtx *op = recog_data.operand;
int len_dummy, icc;
/* Extract insn's operands. */
extract_constrain_insn_cached (insn);
switch (cc)
{
default:
gcc_unreachable();
case CC_PLUS:
avr_out_plus (insn, op, &len_dummy, &icc);
cc = (enum attr_cc) icc;
break;
case CC_LDI:
cc = (op[1] == CONST0_RTX (GET_MODE (op[0]))
&& reg_overlap_mentioned_p (op[0], zero_reg_rtx))
/* Loading zero-reg with 0 uses CLR and thus clobbers cc0. */
? CC_CLOBBER
/* Any other "r,rL" combination does not alter cc0. */
: CC_NONE;
break;
} /* inner switch */
break;
}
} /* outer swicth */
switch (cc)
{
default:
/* Special values like CC_OUT_PLUS from above have been
mapped to "standard" CC_* values so we never come here. */
gcc_unreachable();
break;
case CC_NONE:
/* Insn does not affect CC at all, but it might set some registers
that are stored in cc_status. If such a register is affected by
the current insn, for example by means of a SET or a CLOBBER,
then we must reset cc_status; cf. PR77326.
Unfortunately, set_of cannot be used as reg_overlap_mentioned_p
will abort on COMPARE (which might be found in cc_status.value1/2).
Thus work out the registers set by the insn and regs mentioned
in cc_status.value1/2. */
if (cc_status.value1
|| cc_status.value2)
{
HARD_REG_SET regs_used;
HARD_REG_SET regs_set;
CLEAR_HARD_REG_SET (regs_used);
if (cc_status.value1
&& !CONSTANT_P (cc_status.value1))
{
find_all_hard_regs (cc_status.value1, &regs_used);
}
if (cc_status.value2
&& !CONSTANT_P (cc_status.value2))
{
find_all_hard_regs (cc_status.value2, &regs_used);
}
find_all_hard_reg_sets (insn, &regs_set, false);
if (hard_reg_set_intersect_p (regs_used, regs_set))
{
CC_STATUS_INIT;
}
}
break; // CC_NONE
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_VZN:
/* Insn like INC, DEC, NEG that set Z,N,V. We currently don't make use
of this combination, cf. also PR61055. */
CC_STATUS_INIT;
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;
break;
}
}
/* 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 *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 ? ("breq .+2" CR_TAB
"brpl %0") :
len == 2 ? ("breq .+4" CR_TAB
"brmi .+2" CR_TAB
"rjmp %0") :
("breq .+6" CR_TAB
"brmi .+4" CR_TAB
"jmp %0"));
else
return (len == 1 ? ("breq .+2" CR_TAB
"brge %0") :
len == 2 ? ("breq .+4" CR_TAB
"brlt .+2" CR_TAB
"rjmp %0") :
("breq .+6" CR_TAB
"brlt .+4" CR_TAB
"jmp %0"));
case GTU:
return (len == 1 ? ("breq .+2" CR_TAB
"brsh %0") :
len == 2 ? ("breq .+4" CR_TAB
"brlo .+2" CR_TAB
"rjmp %0") :
("breq .+6" CR_TAB
"brlo .+4" CR_TAB
"jmp %0"));
case LE:
if (cc_prev_status.flags & CC_OVERFLOW_UNUSABLE)
return (len == 1 ? ("breq %0" CR_TAB
"brmi %0") :
len == 2 ? ("breq .+2" CR_TAB
"brpl .+2" CR_TAB
"rjmp %0") :
("breq .+2" CR_TAB
"brpl .+4" CR_TAB
"jmp %0"));
else
return (len == 1 ? ("breq %0" CR_TAB
"brlt %0") :
len == 2 ? ("breq .+2" CR_TAB
"brge .+2" CR_TAB
"rjmp %0") :
("breq .+2" CR_TAB
"brge .+4" CR_TAB
"jmp %0"));
case LEU:
return (len == 1 ? ("breq %0" CR_TAB
"brlo %0") :
len == 2 ? ("breq .+2" CR_TAB
"brsh .+2" CR_TAB
"rjmp %0") :
("breq .+2" CR_TAB
"brsh .+4" CR_TAB
"jmp %0"));
default:
if (reverse)
{
switch (len)
{
case 1:
return "br%k1 %0";
case 2:
return ("br%j1 .+2" CR_TAB
"rjmp %0");
default:
return ("br%j1 .+4" CR_TAB
"jmp %0");
}
}
else
{
switch (len)
{
case 1:
return "br%j1 %0";
case 2:
return ("br%k1 .+2" CR_TAB
"rjmp %0");
default:
return ("br%k1 .+4" CR_TAB
"jmp %0");
}
}
}
return "";
}
/* Worker function for `FINAL_PRESCAN_INSN'. */
/* Output insn cost for next insn. */
void
avr_final_prescan_insn (rtx_insn *insn, rtx *operand ATTRIBUTE_UNUSED,
int num_operands ATTRIBUTE_UNUSED)
{
if (avr_log.rtx_costs)
{
rtx set = single_set (insn);
if (set)
fprintf (asm_out_file, "/* DEBUG: cost = %d. */\n",
set_src_cost (SET_SRC (set), optimize_insn_for_speed_p ()));
else
fprintf (asm_out_file, "/* DEBUG: pattern-cost = %d. */\n",
rtx_cost (PATTERN (insn), INSN, 0,
optimize_insn_for_speed_p()));
}
}
/* Return 0 if undefined, 1 if always true or always false. */
int
avr_simplify_comparison_p (machine_mode mode, RTX_CODE op, rtx x)
{
unsigned int max = (mode == QImode ? 0xff :
mode == HImode ? 0xffff :
mode == PSImode ? 0xffffff :
mode == SImode ? 0xffffffff : 0);
if (max && op && CONST_INT_P (x))
{
if (unsigned_condition (op) != op)
max >>= 1;
if (max != (INTVAL (x) & max)
&& INTVAL (x) != 0xff)
return 1;
}
return 0;
}
/* Worker function for `FUNCTION_ARG_REGNO_P'. */
/* Returns nonzero if REGNO is the number of a hard
register in which function arguments are sometimes passed. */
int
avr_function_arg_regno_p(int r)
{
return (AVR_TINY ? r >= 20 && r <= 25 : r >= 8 && r <= 25);
}
/* Worker function for `INIT_CUMULATIVE_ARGS'. */
/* Initializing the variable cum for the state at the beginning
of the argument list. */
void
avr_init_cumulative_args (CUMULATIVE_ARGS *cum, tree fntype, rtx libname,
tree fndecl ATTRIBUTE_UNUSED)
{
cum->nregs = AVR_TINY ? 6 : 18;
cum->regno = FIRST_CUM_REG;
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 (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;
}
/* Implement `TARGET_FUNCTION_ARG'. */
/* Controls whether a function argument is passed
in a register, and which register. */
static rtx
avr_function_arg (cumulative_args_t cum_v, 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)
return gen_rtx_REG (mode, cum->regno - bytes);
return NULL_RTX;
}
/* Implement `TARGET_FUNCTION_ARG_ADVANCE'. */
/* Update the summarizer variable CUM to advance past an argument
in the argument list. */
static void
avr_function_arg_advance (cumulative_args_t cum_v, 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 will 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])
warning (0, "fixed register %s used to pass parameter to function",
reg_names[regno]);
}
if (cum->nregs <= 0)
{
cum->nregs = 0;
cum->regno = FIRST_CUM_REG;
}
}
/* 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 (cfun->machine->is_interrupt
|| cfun->machine->is_signal
|| cfun->machine->is_naked
|| avr_naked_function_p (decl_callee)
/* FIXME: For OS_task and OS_main, this might be over-conservative. */
|| (avr_OS_task_function_p (decl_callee)
!= cfun->machine->is_OS_task)
|| (avr_OS_main_function_p (decl_callee)
!= cfun->machine->is_OS_main))
{
return false;
}
return true;
}
/***********************************************************************
Functions for outputting various mov's for a various modes
************************************************************************/
/* Return true if a value of mode MODE is read from flash by
__load_* function from libgcc. */
bool
avr_load_libgcc_p (rtx op)
{
machine_mode mode = GET_MODE (op);
int n_bytes = GET_MODE_SIZE (mode);
return (n_bytes > 2
&& !AVR_HAVE_LPMX
&& avr_mem_flash_p (op));
}
/* Return true if a value of mode MODE is read by __xload_* function. */
bool
avr_xload_libgcc_p (machine_mode mode)
{
int n_bytes = GET_MODE_SIZE (mode);
return (n_bytes > 1
|| avr_n_flash > 1);
}
/* Fixme: This is a hack because secondary reloads don't works as expected.
Find an unused d-register to be used as scratch in INSN.
EXCLUDE is either NULL_RTX or some register. In the case where EXCLUDE
is a register, skip all possible return values that overlap EXCLUDE.
The policy for the returned register is similar to that of
`reg_unused_after', i.e. the returned register may overlap the SET_DEST
of INSN.
Return a QImode d-register or NULL_RTX if nothing found. */
static rtx
avr_find_unused_d_reg (rtx_insn *insn, rtx exclude)
{
int regno;
bool isr_p = (avr_interrupt_function_p (current_function_decl)
|| avr_signal_function_p (current_function_decl));
for (regno = 16; regno < 32; regno++)
{
rtx reg = all_regs_rtx[regno];
if ((exclude
&& reg_overlap_mentioned_p (exclude, reg))
|| fixed_regs[regno])
{
continue;
}
/* Try non-live register */
if (!df_regs_ever_live_p (regno)
&& (TREE_THIS_VOLATILE (current_function_decl)
|| cfun->machine->is_OS_task
|| cfun->machine->is_OS_main
|| (!isr_p && call_used_regs[regno])))
{
return reg;
}
/* Any live register can be used if it is unused after.
Prologue/epilogue will care for it as needed. */
if (df_regs_ever_live_p (regno)
&& reg_unused_after (insn, reg))
{
return reg;
}
}
return NULL_RTX;
}
/* Helper function for the next function in the case where only restricted
version of LPM instruction is available. */
static const char*
avr_out_lpm_no_lpmx (rtx_insn *insn, rtx *xop, int *plen)
{
rtx dest = xop[0];
rtx addr = xop[1];
int n_bytes = GET_MODE_SIZE (GET_MODE (dest));
int regno_dest;
regno_dest = REGNO (dest);
/* The implicit target register of LPM. */
xop[3] = lpm_reg_rtx;
switch (GET_CODE (addr))
{
default:
gcc_unreachable();
case REG:
gcc_assert (REG_Z == REGNO (addr));
switch (n_bytes)
{
default:
gcc_unreachable();
case 1:
avr_asm_len ("%4lpm", xop, plen, 1);
if (regno_dest != LPM_REGNO)
avr_asm_len ("mov %0,%3", xop, plen, 1);
return "";
case 2:
if (REGNO (dest) == REG_Z)
return avr_asm_len ("%4lpm" CR_TAB
"push %3" CR_TAB
"adiw %2,1" CR_TAB
"%4lpm" CR_TAB
"mov %B0,%3" CR_TAB
"pop %A0", xop, plen, 6);
avr_asm_len ("%4lpm" CR_TAB
"mov %A0,%3" CR_TAB
"adiw %2,1" CR_TAB
"%4lpm" CR_TAB
"mov %B0,%3", xop, plen, 5);
if (!reg_unused_after (insn, addr))
avr_asm_len ("sbiw %2,1", xop, plen, 1);
break; /* 2 */
}
break; /* REG */
case POST_INC:
gcc_assert (REG_Z == REGNO (XEXP (addr, 0))
&& n_bytes <= 4);
if (regno_dest == LPM_REGNO)
avr_asm_len ("%4lpm" CR_TAB
"adiw %2,1", xop, plen, 2);
else
avr_asm_len ("%4lpm" CR_TAB
"mov %A0,%3" CR_TAB
"adiw %2,1", xop, plen, 3);
if (n_bytes >= 2)
avr_asm_len ("%4lpm" CR_TAB
"mov %B0,%3" CR_TAB
"adiw %2,1", xop, plen, 3);
if (n_bytes >= 3)
avr_asm_len ("%4lpm" CR_TAB
"mov %C0,%3" CR_TAB
"adiw %2,1", xop, plen, 3);
if (n_bytes >= 4)
avr_asm_len ("%4lpm" CR_TAB
"mov %D0,%3" CR_TAB
"adiw %2,1", xop, plen, 3);
break; /* POST_INC */
} /* switch CODE (addr) */
return "";
}
/* If PLEN == NULL: Ouput instructions to load a value from a memory location
OP[1] in AS1 to register OP[0].
If PLEN != 0 set *PLEN to the length in words of the instruction sequence.
Return "". */
const char*
avr_out_lpm (rtx_insn *insn, rtx *op, int *plen)
{
rtx xop[7];
rtx dest = op[0];
rtx src = SET_SRC (single_set (insn));
rtx addr;
int n_bytes = GET_MODE_SIZE (GET_MODE (dest));
int segment;
RTX_CODE code;
addr_space_t as = MEM_ADDR_SPACE (src);
if (plen)
*plen = 0;
if (MEM_P (dest))
{
warning (0, "writing to address space %qs not supported",
avr_addrspace[MEM_ADDR_SPACE (dest)].name);
return "";
}
addr = XEXP (src, 0);
code = GET_CODE (addr);
gcc_assert (REG_P (dest));
gcc_assert (REG == code || POST_INC == code);
xop[0] = dest;
xop[1] = addr;
xop[2] = lpm_addr_reg_rtx;
xop[4] = xstring_empty;
xop[5] = tmp_reg_rtx;
xop[6] = XEXP (rampz_rtx, 0);
segment = avr_addrspace[as].segment;
/* Set RAMPZ as needed. */
if (segment)
{
xop[4] = GEN_INT (segment);
xop[3] = avr_find_unused_d_reg (insn, lpm_addr_reg_rtx);
if (xop[3] != NULL_RTX)
{
avr_asm_len ("ldi %3,%4" CR_TAB
"out %i6,%3", xop, plen, 2);
}
else if (segment == 1)
{
avr_asm_len ("clr %5" CR_TAB
"inc %5" CR_TAB
"out %i6,%5", xop, plen, 3);
}
else
{
avr_asm_len ("mov %5,%2" CR_TAB
"ldi %2,%4" CR_TAB
"out %i6,%2" CR_TAB
"mov %2,%5", xop, plen, 4);
}
xop[4] = xstring_e;
if (!AVR_HAVE_ELPMX)
return avr_out_lpm_no_lpmx (insn, xop, plen);
}
else if (!AVR_HAVE_LPMX)
{
return avr_out_lpm_no_lpmx (insn, xop, plen);
}
/* We have [E]LPMX: Output reading from Flash the comfortable way. */
switch (GET_CODE (addr))
{
default:
gcc_unreachable();
case REG:
gcc_assert (REG_Z == REGNO (addr));
switch (n_bytes)
{
default:
gcc_unreachable();
case 1:
return avr_asm_len ("%4lpm %0,%a2", xop, plen, 1);
case 2:
if (REGNO (dest) == REG_Z)
return avr_asm_len ("%4lpm %5,%a2+" CR_TAB
"%4lpm %B0,%a2" CR_TAB
"mov %A0,%5", xop, plen, 3);
else
{
avr_asm_len ("%4lpm %A0,%a2+" CR_TAB
"%4lpm %B0,%a2", xop, plen, 2);