blob: 4ed390e4cf9ec39c97c2dc67da48defa5dcf4f3e [file] [log] [blame]
/* Subroutines for insn-output.cc for ATMEL AVR micro controllers
Copyright (C) 1998-2022 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/>. */
#define IN_TARGET_CODE 1
#include "config.h"
#include "system.h"
#include "intl.h"
#include "coretypes.h"
#include "backend.h"
#include "target.h"
#include "rtl.h"
#include "tree.h"
#include "stringpool.h"
#include "attribs.h"
#include "cgraph.h"
#include "c-family/c-common.h"
#include "cfghooks.h"
#include "df.h"
#include "memmodel.h"
#include "tm_p.h"
#include "optabs.h"
#include "regs.h"
#include "emit-rtl.h"
#include "recog.h"
#include "conditions.h"
#include "insn-attr.h"
#include "reload.h"
#include "varasm.h"
#include "calls.h"
#include "stor-layout.h"
#include "output.h"
#include "explow.h"
#include "expr.h"
#include "langhooks.h"
#include "cfgrtl.h"
#include "builtins.h"
#include "context.h"
#include "tree-pass.h"
#include "print-rtl.h"
#include "rtl-iter.h"
/* This file should be included last. */
#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))
/* 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)
/* (AVR_TINY only): Symbol has attribute progmem */
#define AVR_SYMBOL_FLAG_TINY_PM \
(SYMBOL_FLAG_MACH_DEP << 7)
/* (AVR_TINY only): Symbol has attribute absdata */
#define AVR_SYMBOL_FLAG_TINY_ABSDATA \
(SYMBOL_FLAG_MACH_DEP << 8)
#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, machine_mode, 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;
/* Condition Code register RTX (reg:CC REG_CC) */
extern GTY(()) rtx cc_reg_rtx;
rtx cc_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;
/* 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;
}
/* 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)
{
machine_mode mode = GET_MODE (xval);
if (VOIDmode == mode)
mode = SImode;
for (int 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 ((pop_mask & (1 << popcount_hwi (val8))) == 0)
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).require (), x, mode, 0);
}
namespace {
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 const pass_data avr_pass_data_casesi =
{
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
0 // todo_flags_finish
};
class avr_pass_casesi : public rtl_opt_pass
{
public:
avr_pass_casesi (gcc::context *ctxt, const char *name)
: rtl_opt_pass (avr_pass_data_casesi, ctxt)
{
this->name = name;
}
void avr_rest_of_handle_casesi (function*);
virtual bool gate (function*) { return optimize > 0; }
virtual unsigned int execute (function *func)
{
avr_rest_of_handle_casesi (func);
return 0;
}
}; // avr_pass_casesi
} // anon namespace
rtl_opt_pass*
make_avr_pass_recompute_notes (gcc::context *ctxt)
{
return new avr_pass_recompute_notes (ctxt, "avr-notes-free-cfg");
}
rtl_opt_pass*
make_avr_pass_casesi (gcc::context *ctxt)
{
return new avr_pass_casesi (ctxt, "avr-casesi");
}
/* Make one parallel insn with all the patterns from insns i[0]..i[5]. */
static rtx_insn*
avr_parallel_insn_from_insns (rtx_insn *i[5])
{
rtvec vec = gen_rtvec (5, PATTERN (i[0]), PATTERN (i[1]), PATTERN (i[2]),
PATTERN (i[3]), PATTERN (i[4]));
start_sequence();
emit (gen_rtx_PARALLEL (VOIDmode, vec));
rtx_insn *insn = get_insns();
end_sequence();
return insn;
}
/* Return true if we see an insn stream generated by casesi expander together
with an extension to SImode of the switch value.
If this is the case, fill in the insns from casesi to INSNS[1..5] and
the SImode extension to INSNS[0]. Moreover, extract the operands of
pattern casesi_<mode>_sequence forged from the sequence to recog_data. */
static bool
avr_is_casesi_sequence (basic_block bb, rtx_insn *insn, rtx_insn *insns[5])
{
rtx set_4, set_0;
/* A first and quick test for a casesi sequences. As a side effect of
the test, harvest respective insns to INSNS[0..4]. */
if (!(JUMP_P (insns[4] = insn)
// casesi is the only insn that comes up with UNSPEC_INDEX_JMP,
// hence the following test ensures that we are actually dealing
// with code from casesi.
&& (set_4 = single_set (insns[4]))
&& UNSPEC == GET_CODE (SET_SRC (set_4))
&& UNSPEC_INDEX_JMP == XINT (SET_SRC (set_4), 1)
&& (insns[3] = prev_real_insn (insns[4]))
&& (insns[2] = prev_real_insn (insns[3]))
&& (insns[1] = prev_real_insn (insns[2]))
// Insn prior to casesi.
&& (insns[0] = prev_real_insn (insns[1]))
&& (set_0 = single_set (insns[0]))
&& extend_operator (SET_SRC (set_0), SImode)))
{
return false;
}
if (dump_file)
{
fprintf (dump_file, ";; Sequence from casesi in "
"[bb %d]:\n\n", bb->index);
for (int i = 0; i < 5; i++)
print_rtl_single (dump_file, insns[i]);
}
/* We have to deal with quite some operands. Extracting them by hand
would be tedious, therefore wrap the insn patterns into a parallel,
run recog against it and then use insn extract to get the operands. */
rtx_insn *xinsn = avr_parallel_insn_from_insns (insns);
INSN_CODE (xinsn) = recog (PATTERN (xinsn), xinsn, NULL /* num_clobbers */);
/* Failing to recognize means that someone changed the casesi expander or
that some passes prior to this one performed some unexpected changes.
Gracefully drop such situations instead of aborting. */
if (INSN_CODE (xinsn) < 0)
{
if (dump_file)
fprintf (dump_file, ";; Sequence not recognized, giving up.\n\n");
return false;
}
gcc_assert (CODE_FOR_casesi_qi_sequence == INSN_CODE (xinsn)
|| CODE_FOR_casesi_hi_sequence == INSN_CODE (xinsn));
extract_insn (xinsn);
// Assert on the anatomy of xinsn's operands we are going to work with.
gcc_assert (recog_data.n_operands == 11);
gcc_assert (recog_data.n_dups == 4);
if (dump_file)
{
fprintf (dump_file, ";; Operands extracted:\n");
for (int i = 0; i < recog_data.n_operands; i++)
avr_fdump (dump_file, ";; $%d = %r\n", i, recog_data.operand[i]);
fprintf (dump_file, "\n");
}
return true;
}
/* Perform some extra checks on operands of casesi_<mode>_sequence.
Not all operand dependencies can be described by means of predicates.
This function performs left over checks and should always return true.
Returning false means that someone changed the casesi expander but did
not adjust casesi_<mode>_sequence. */
bool
avr_casei_sequence_check_operands (rtx *xop)
{
rtx sub_5 = NULL_RTX;
if (AVR_HAVE_EIJMP_EICALL
// The last clobber op of the tablejump.
&& xop[8] == all_regs_rtx[24])
{
// $6 is: (subreg:SI ($5) 0)
sub_5 = xop[6];
}
if (!AVR_HAVE_EIJMP_EICALL
// $6 is: (plus:HI (subreg:SI ($5) 0)
// (label_ref ($3)))
&& PLUS == GET_CODE (xop[6])
&& LABEL_REF == GET_CODE (XEXP (xop[6], 1))
&& rtx_equal_p (xop[3], XEXP (XEXP (xop[6], 1), 0))
// The last clobber op of the tablejump.
&& xop[8] == const0_rtx)
{
sub_5 = XEXP (xop[6], 0);
}
if (sub_5
&& SUBREG_P (sub_5)
&& SUBREG_BYTE (sub_5) == 0
&& rtx_equal_p (xop[5], SUBREG_REG (sub_5)))
return true;
if (dump_file)
fprintf (dump_file, "\n;; Failed condition for casesi_<mode>_sequence\n\n");
return false;
}
/* INSNS[1..4] is a sequence as generated by casesi and INSNS[0] is an
extension of an 8-bit or 16-bit integer to SImode. XOP contains the
operands of INSNS as extracted by insn_extract from pattern
casesi_<mode>_sequence:
$0: SImode reg switch value as result of $9.
$1: Negative of smallest index in switch.
$2: Number of entries in switch.
$3: Label to table.
$4: Label if out-of-bounds.
$5: $0 + $1.
$6: 3-byte PC: subreg:HI ($5) + label_ref ($3)
2-byte PC: subreg:HI ($5)
$7: HI reg index into table (Z or pseudo)
$8: R24 or const0_rtx (to be clobbered)
$9: Extension to SImode of an 8-bit or 16-bit integer register $10.
$10: QImode or HImode register input of $9.
Try to optimize this sequence, i.e. use the original HImode / QImode
switch value instead of SImode. */
static void
avr_optimize_casesi (rtx_insn *insns[5], rtx *xop)
{
// Original mode of the switch value; this is QImode or HImode.
machine_mode mode = GET_MODE (xop[10]);
// How the original switch value was extended to SImode; this is
// SIGN_EXTEND or ZERO_EXTEND.
enum rtx_code code = GET_CODE (xop[9]);
// Lower index, upper index (plus one) and range of case calues.
HOST_WIDE_INT low_idx = -INTVAL (xop[1]);
HOST_WIDE_INT num_idx = INTVAL (xop[2]);
HOST_WIDE_INT hig_idx = low_idx + num_idx;
// Maximum ranges of (un)signed QImode resp. HImode.
unsigned umax = QImode == mode ? 0xff : 0xffff;
int imax = QImode == mode ? 0x7f : 0x7fff;
int imin = -imax - 1;
// Testing the case range and whether it fits into the range of the
// (un)signed mode. This test should actually always pass because it
// makes no sense to have case values outside the mode range. Notice
// that case labels which are unreachable because they are outside the
// mode of the switch value (e.g. "case -1" for uint8_t) have already
// been thrown away by the middle-end.
if (SIGN_EXTEND == code
&& low_idx >= imin
&& hig_idx <= imax)
{
// ok
}
else if (ZERO_EXTEND == code
&& low_idx >= 0
&& (unsigned) hig_idx <= umax)
{
// ok
}
else
{
if (dump_file)
fprintf (dump_file, ";; Case ranges too big, giving up.\n\n");
return;
}
// Do normalization of switch value $10 and out-of-bound check in its
// original mode instead of in SImode. Use a newly created pseudo.
// This will replace insns[1..2].
start_sequence();
rtx_insn *seq1, *seq2, *last1, *last2;
rtx reg = copy_to_mode_reg (mode, xop[10]);
rtx (*gen_add)(rtx,rtx,rtx) = QImode == mode ? gen_addqi3 : gen_addhi3;
rtx (*gen_cbranch)(rtx,rtx,rtx,rtx)
= QImode == mode ? gen_cbranchqi4 : gen_cbranchhi4;
emit_insn (gen_add (reg, reg, gen_int_mode (-low_idx, mode)));
rtx op0 = reg; rtx op1 = gen_int_mode (num_idx, mode);
rtx labelref = copy_rtx (xop[4]);
emit_jump_insn (gen_cbranch (gen_rtx_fmt_ee (GTU, VOIDmode, op0, op1),
op0, op1,
labelref));
seq1 = get_insns();
last1 = get_last_insn();
end_sequence();
emit_insn_after (seq1, insns[2]);
// After the out-of-bounds test and corresponding branch, use a
// 16-bit index. If QImode is used, extend it to HImode first.
// This will replace insns[4].
start_sequence();
if (QImode == mode)
reg = force_reg (HImode, gen_rtx_fmt_e (code, HImode, reg));
rtx pat_4 = AVR_3_BYTE_PC
? gen_movhi (xop[7], reg)
: gen_addhi3 (xop[7], reg, gen_rtx_LABEL_REF (VOIDmode, xop[3]));
emit_insn (pat_4);
seq2 = get_insns();
last2 = get_last_insn();
end_sequence();
emit_insn_after (seq2, insns[3]);
if (dump_file)
{
fprintf (dump_file, ";; New insns: ");
for (rtx_insn *insn = seq1; ; insn = NEXT_INSN (insn))
{
fprintf (dump_file, "%d, ", INSN_UID (insn));
if (insn == last1)
break;
}
for (rtx_insn *insn = seq2; ; insn = NEXT_INSN (insn))
{
fprintf (dump_file, "%d%s", INSN_UID (insn),
insn == last2 ? ".\n\n" : ", ");
if (insn == last2)
break;
}
fprintf (dump_file, ";; Deleting insns: %d, %d, %d.\n\n",
INSN_UID (insns[1]), INSN_UID (insns[2]), INSN_UID (insns[3]));
}
// Pseudodelete the SImode and subreg of SImode insns. We don't care
// about the extension insns[0]: Its result is now unused and other
// passes will clean it up.
SET_INSN_DELETED (insns[1]);
SET_INSN_DELETED (insns[2]);
SET_INSN_DELETED (insns[3]);
}
void
avr_pass_casesi::avr_rest_of_handle_casesi (function *func)
{
basic_block bb;
FOR_EACH_BB_FN (bb, func)
{
rtx_insn *insn, *insns[5];
FOR_BB_INSNS (bb, insn)
{
if (avr_is_casesi_sequence (bb, insn, insns))
{
avr_optimize_casesi (insns, recog_data.operand);
}
}
}
}
/* 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 (mcu->name == NULL)
{
/* 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 (strcmp (mcu->name, avr_mmcu) == 0
// Is this a proper architecture ?
&& mcu->macro == NULL)
{
avr_arch = &avr_arch_types[mcu->arch_id];
if (avr_n_flash < 0)
avr_n_flash = 1 + (mcu->flash_size - 1) / 0x10000;
return true;
}
}
return false;
}
/* Implement `TARGET_OPTION_OVERRIDE'. */
static void
avr_option_override (void)
{
/* caller-save.cc 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.cc: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 !defined (HAVE_AS_AVR_MGCCISR_OPTION)
avr_gasisr_prologues = 0;
#endif
if (!avr_set_core_architecture())
return;
/* Sould be set by avr-common.cc */
gcc_assert (avr_long_double >= avr_double && avr_double >= 32);
/* 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();
}
/* 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)
{
for (int 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];
cc_reg_rtx = gen_rtx_REG (CCmode, REG_CC);
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];
if (r == REG_CC)
return CC_REG;
return ALL_REGS;
}
/* Implement `TARGET_SCALAR_MODE_SUPPORTED_P'. */
static bool
avr_scalar_mode_supported_p (scalar_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");
}
/* Return nonzero if FUNC is a no_gccisr function as specified
by the "no_gccisr" attribute. */
static int
avr_no_gccisr_function_p (tree func)
{
return avr_lookup_function_attribute1 (func, "no_gccisr");
}
/* Implement `TARGET_SET_CURRENT_FUNCTION'. */
/* Sanity cheching for above function attributes. */
static void
avr_set_current_function (tree decl)
{
if (decl == NULL_TREE
|| current_function_decl == NULL_TREE
|| current_function_decl == error_mark_node
|| ! cfun->machine
|| cfun->machine->attributes_checked_p)
return;
location_t 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);
cfun->machine->is_no_gccisr = avr_no_gccisr_function_p (decl);
const char *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_signal || cfun->machine->is_interrupt))
error_at (loc, "function attributes %qs and %qs are mutually exclusive",
"OS_task", isr);
if (cfun->machine->is_OS_main
&& (cfun->machine->is_signal || cfun->machine->is_interrupt))
error_at (loc, "function attributes %qs and %qs are mutually exclusive",
"OS_main", isr);
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 (!startswith (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 (strcmp ("ISR", name) == 0
|| strcmp ("INTERRUPT", name) == 0
|| strcmp ("SIGNAL", name) == 0)
{
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
? (HOST_WIDE_INT) crtl->outgoing_args_size : 0);
}
/* Implement TARGET_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. */
static HOST_WIDE_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 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 (int 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_or_fixed_reg_p (reg))
|| (df_regs_ever_live_p (reg)
&& (int_or_sig_p || !call_used_or_fixed_reg_p (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;
// If FROM is ARG_POINTER_REGNUM, we are not in an ISR as ISRs
// might not have arguments. Hence the following is not affected
// by gasisr prologues.
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);
SET_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.cc:build_common_tree_nodes().
This must run before c-cppbuiltin.cc: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 ();
}
/* 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");
cfun->machine->use_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 live_seq = 0;
int cur_seq = 0;
for (int 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_or_fixed_reg_p (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;
}
namespace {
static const pass_data avr_pass_data_pre_proep =
{
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
0 // todo_flags_finish
};
class avr_pass_pre_proep : public rtl_opt_pass
{
public:
avr_pass_pre_proep (gcc::context *ctxt, const char *name)
: rtl_opt_pass (avr_pass_data_pre_proep, ctxt)
{
this->name = name;
}
void compute_maybe_gasisr (function*);
virtual unsigned int execute (function *fun)
{
if (avr_gasisr_prologues
// Whether this function is an ISR worth scanning at all.
&& !fun->machine->is_no_gccisr
&& (fun->machine->is_interrupt
|| fun->machine->is_signal)
&& !cfun->machine->is_naked
// Paranoia: Non-local gotos and labels that might escape.
&& !cfun->calls_setjmp
&& !cfun->has_nonlocal_label
&& !cfun->has_forced_label_in_static)
{
compute_maybe_gasisr (fun);
}
return 0;
}
}; // avr_pass_pre_proep
} // anon namespace
rtl_opt_pass*
make_avr_pass_pre_proep (gcc::context *ctxt)
{
return new avr_pass_pre_proep (ctxt, "avr-pre-proep");
}
/* Set fun->machine->gasisr.maybe provided we don't find anything that
prohibits GAS generating parts of ISR prologues / epilogues for us. */
void
avr_pass_pre_proep::compute_maybe_gasisr (function *fun)
{
// Don't use BB iterators so that we see JUMP_TABLE_DATA.
for (rtx_insn *insn = get_insns (); insn; insn = NEXT_INSN (insn))
{
// Transparent calls always use [R]CALL and are filtered out by GAS.
// ISRs don't use -mcall-prologues, hence what remains to be filtered
// out are open coded (tail) calls.
if (CALL_P (insn))
return;
// __tablejump2__ clobbers something and is targeted by JMP so
// that GAS won't see its usage.
if (AVR_HAVE_JMP_CALL
&& JUMP_TABLE_DATA_P (insn))
return;
// Non-local gotos not seen in *FUN.
if (JUMP_P (insn)
&& find_reg_note (insn, REG_NON_LOCAL_GOTO, NULL_RTX))
return;
}
fun->machine->gasisr.maybe = 1;
}
/* Obtain the length sequence of insns. */
int
get_sequence_length (rtx_insn *insns)
{
int length = 0;
for (rtx_insn *insn = insns; 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));
}
/* Unset a bit in *SET. If successful, return the respective bit number.
Otherwise, return -1 and *SET is unaltered. */
static int
avr_hregs_split_reg (HARD_REG_SET *set)
{
for (int regno = 0; regno < 32; regno++)
if (TEST_HARD_REG_BIT (*set, regno))
{
// Don't remove a register from *SET which might indicate that
// some RAMP* register might need ISR prologue / epilogue treatment.
if (AVR_HAVE_RAMPX
&& (REG_X == regno || REG_X + 1 == regno)
&& TEST_HARD_REG_BIT (*set, REG_X)
&& TEST_HARD_REG_BIT (*set, REG_X + 1))
continue;
if (AVR_HAVE_RAMPY
&& !frame_pointer_needed
&& (REG_Y == regno || REG_Y + 1 == regno)
&& TEST_HARD_REG_BIT (*set, REG_Y)
&& TEST_HARD_REG_BIT (*set, REG_Y + 1))
continue;
if (AVR_HAVE_RAMPZ
&& (REG_Z == regno || REG_Z + 1 == regno)
&& TEST_HARD_REG_BIT (*set, REG_Z)
&& TEST_HARD_REG_BIT (*set, REG_Z + 1))
continue;
CLEAR_HARD_REG_BIT (*set, regno);
return regno;
}
return -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 (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 register TREG.
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, int treg)
{
rtx_insn *insn;
gcc_assert (MEM_P (sfr));
/* IN treg, IO(SFR) */
insn = emit_move_insn (all_regs_rtx[treg], sfr);
if (frame_related_p)
RTX_FRAME_RELATED_P (insn) = 1;
/* PUSH treg */
emit_push_byte (treg, 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 ((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 (m, r));
}
cfun->machine->stack_usage += size + live_seq;
}
else /* !minimize */
{
for (int 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 (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 (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 (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. */
int n_rcall = size / (AVR_3_BYTE_PC ? 3 : 2);
if (avr_sp_immediate_operand (gen_int_mode (-size, HImode), HImode)
// Don't use more than 3 RCALLs.
&& n_rcall <= 3)
{
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 (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)
{
int treg = AVR_TMP_REGNO;
/* Enable interrupts. */
if (cfun->machine->is_interrupt)
emit_insn (gen_enable_interrupt ());
if (cfun->machine->gasisr.maybe)
{
/* Let GAS PR21472 emit prologue preamble for us which handles SREG,
ZERO_REG and TMP_REG and one additional, optional register for
us in an optimal way. This even scans through inline asm. */
cfun->machine->gasisr.yes = 1;
// The optional reg or TMP_REG if we don't need one. If we need one,
// remove that reg from SET so that it's not puhed / popped twice.
// We also use it below instead of TMP_REG in some places.
treg = avr_hregs_split_reg (&set);
if (treg < 0)
treg = AVR_TMP_REGNO;
cfun->machine->gasisr.regno = treg;
// The worst case of pushes. The exact number can be inferred
// at assembly time by magic expression __gcc_isr.n_pushed.
cfun->machine->stack_usage += 3 + (treg != AVR_TMP_REGNO);
// Emit a Prologue chunk. Epilogue chunk(s) might follow.
// The final Done chunk is emit by final postscan.
emit_insn (gen_gasisr (GEN_INT (GASISR_Prologue), GEN_INT (treg)));
}
else // !TARGET_GASISR_PROLOGUES: Classic, dumb prologue preamble.
{
/* 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 */, AVR_TMP_REGNO);
/* 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 */, true /* clr */, treg);
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 */, true /* clr */, treg);
}
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 */, true /* clr */, treg);
}
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 */, AVR_HAVE_RAMPD, treg);
}
} /* is_interrupt is_signal */
avr_prologue_setup_frame (size, set);
if (flag_stack_usage_info)
current_function_static_stack_size
= cfun->machine->stack_usage + INCOMING_FRAME_SP_OFFSET;
}
/* 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",
(HOST_WIDE_INT) get_frame_size());
if (!cfun->machine->gasisr.yes)
{
fprintf (file, "/* stack size = %d */\n", cfun->machine->stack_usage);
// Create symbol stack offset 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);
}
else
{
int used_by_gasisr = 3 + (cfun->machine->gasisr.regno != AVR_TMP_REGNO);
int to = cfun->machine->stack_usage;
int from = to - used_by_gasisr;
// Number of pushed regs is only known at assembly-time.
fprintf (file, "/* stack size = %d...%d */\n", from , to);
fprintf (file, ".L__stack_usage = %d + __gcc_isr.n_pushed\n", from);
}
}
/* 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 (reg, mem));
}
/* Output RTL epilogue. */
void
avr_expand_epilogue (bool sibcall_p)
{
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. */
int treg = AVR_TMP_REGNO;
if (isr_p
&& cfun->machine->gasisr.yes)
{
treg = cfun->machine->gasisr.regno;
CLEAR_HARD_REG_BIT (set, treg);
}
for (int 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 (treg);
emit_move_insn (rampz_rtx, all_regs_rtx[treg]);
}
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 (treg);
emit_move_insn (rampy_rtx, all_regs_rtx[treg]);
}
if (AVR_HAVE_RAMPX
&& TEST_HARD_REG_BIT (set, REG_X)
&& TEST_HARD_REG_BIT (set, REG_X + 1))
{
emit_pop_byte (treg);
emit_move_insn (rampx_rtx, all_regs_rtx[treg]);
}
if (AVR_HAVE_RAMPD)
{
emit_pop_byte (treg);
emit_move_insn (rampd_rtx, all_regs_rtx[treg]);
}
if (cfun->machine->gasisr.yes)
{
// Emit an Epilogue chunk.
emit_insn (gen_gasisr (GEN_INT (GASISR_Epilogue),
GEN_INT (cfun->machine->gasisr.regno)));
}
else // !TARGET_GASISR_PROLOGUES
{
/* 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)
{
app_disable();
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.cc will skip these addresses. lower-subreg.cc 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);
}
/* Return true if rtx X is a CONST_INT, CONST or SYMBOL_REF
address with the `absdata' variable attribute, i.e. respective
data can be read / written by LDS / STS instruction.
This is used only for AVR_TINY. */
static bool
avr_address_tiny_absdata_p (rtx x, machine_mode mode)
{
if (CONST == GET_CODE (x))
x = XEXP (XEXP (x, 0), 0);
if (SYMBOL_REF_P (x))
return SYMBOL_REF_FLAGS (x) & AVR_SYMBOL_FLAG_TINY_ABSDATA;
if (CONST_INT_P (x)
&& IN_RANGE (INTVAL (x), 0, 0xc0 - GET_MODE_SIZE (mode)))
return true;
return false;
}
/* 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 = avr_address_tiny_absdata_p (x, 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 (AVR_TINY)
{
if (CONSTANT_ADDRESS_P (x)
&& ! avr_address_tiny_absdata_p (x, mode))
{
x = force_reg (Pmode, x);
}
}
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))
&& reg_equiv_constant (REGNO (XEXP (x, 0))) == 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;
}
/* 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 (plen == NULL)
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)
{
bool cc_overflow_unusable = false;
switch (code)
{
case NE:
return "ne";
case EQ:
return "eq";
case GE:
if (cc_overflow_unusable)
return "pl";
else
return "ge";
case LT:
if (cc_overflow_unusable)
return "mi";
else
return "lt";
case GEU:
return "sh";
case LTU:
return "lo";
default:
gcc_unreachable ();
}
return "";
}
/* Return true if rtx X is a CONST or SYMBOL_REF with progmem.
This must be used for AVR_TINY only because on other cores
the flash memory is not visible in the RAM address range and
cannot be read by, say, LD instruction. */
static bool
avr_address_tiny_pm_p (rtx x)
{
if (CONST == GET_CODE (x))
x = XEXP (XEXP (x, 0), 0);
if (SYMBOL_REF_P (x))
return SYMBOL_REF_FLAGS (x) & AVR_SYMBOL_FLAG_TINY_PM;
return false;
}
/* Implement `TARGET_PRINT_OPERAND_ADDRESS'. */
/* Output ADDR to FILE as address. */
static void
avr_print_operand_address (FILE *file, machine_mode /*mode*/, rtx addr)
{
if (AVR_TINY
&& avr_address_tiny_pm_p (addr))
{
addr = plus_constant (Pmode, addr, avr_arch->flash_pm_offset);
}
switch (GET_CODE (addr))
{
case REG:
fprintf (file, "%s", 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 && CONST_INT_P (XEXP (x, 1)))
{
/* 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, "%s", reg_names[REGNO (op) + ef]);
}
else if (code == 'I' || code == 'J')
{
rtx op = XEXP (XEXP (x, 0), 0);
fprintf (file, "%s", 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, "%s", 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, VOIDmode, 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')
/* X, Y, Z */
avr_print_operand_address (file, VOIDmode, XEXP (addr, 0));
else
avr_print_operand (file, XEXP (addr, 0), 0); /* r26, r28, r30 */
}
else if (GET_CODE (addr) == PLUS)
{
avr_print_operand_address (file, VOIDmode, 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, VOIDmode, addr);
}
else if (code == 'i')
{
if (SYMBOL_REF_P (x) && (SYMBOL_REF_FLAGS (x) & SYMBOL_FLAG_IO))
avr_print_operand_address
(file, VOIDmode, 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 (text_segment_operand (x, VOIDmode) == 0)
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 (CONST_DOUBLE_P (x))
{
long val;
if (GET_MODE (x) != SFmode)
fatal_insn ("internal compiler error. Unknown mode:", x);
REAL_VALUE_TO_TARGET_SINGLE (*CONST_DOUBLE_REAL_VALUE (x), 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, VOIDmode, x);
}
/* Implement TARGET_USE_BY_PIECES_INFRASTRUCTURE_P. */
/* Prefer sequence of loads/stores for moves of size upto
two - two pairs of load/store instructions are always better
than the 5 instruction sequence for a loop (1 instruction
for loop counter setup, and 4 for the body of the loop). */
static bool
avr_use_by_pieces_infrastructure_p (unsigned HOST_WIDE_INT size,
unsigned int align ATTRIBUTE_UNUSED,
enum by_pieces_operation op,
bool speed_p)
{
if (op != MOVE_BY_PIECES
|| (speed_p && size > MOVE_MAX_PIECES))
return default_use_by_pieces_infrastructure_p (size, align, op, speed_p);
return size <= MOVE_MAX_PIECES;
}
/* 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 (IN_RANGE (jump_distance, -63, 62))
return 1;
else if (IN_RANGE (jump_distance, -2046, 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);
bool cc_overflow_unusable = false;
switch (cond)
{
case GT:
if (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_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), GET_MODE (SET_DEST (set)),
optimize_insn_for_speed_p ()));
else
fprintf (asm_out_file, "/* DEBUG: pattern-cost = %d. */\n",
rtx_cost (PATTERN (insn), VOIDmode, INSN, 0,
optimize_insn_for_speed_p()));
}
if (avr_log.insn_addresses)
fprintf (asm_out_file, ";; ADDR = %d\n",
(int) INSN_ADDRESSES (INSN_UID (insn)));
}
/* Implement `TARGET_ASM_FINAL_POSTSCAN_INSN'. */
/* When GAS generates (parts of) ISR prologue / epilogue for us, we must
hint GAS about the end of the code to scan. There migh be code located
after the last epilogue. */
static void
avr_asm_final_postscan_insn (FILE *stream, rtx_insn *insn, rtx*, int)
{
if (cfun->machine->gasisr.yes
&& !next_real_insn (insn))
{
app_disable();
fprintf (stream, "\t__gcc_isr %d,r%d\n", GASISR_Done,
cfun->machine->gasisr.regno);
}
}
/* 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 ? IN_RANGE (r, 20, 25) : IN_RANGE (r, 8, 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;