| /* 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; |