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