| /* Copyright (C) 1997-2021 Free Software Foundation, Inc. |
| Contributed by Red Hat, Inc. |
| |
| 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 "coretypes.h" |
| #include "backend.h" |
| #include "target.h" |
| #include "rtl.h" |
| #include "tree.h" |
| #include "df.h" |
| #include "memmodel.h" |
| #include "tm_p.h" |
| #include "stringpool.h" |
| #include "attribs.h" |
| #include "optabs.h" |
| #include "regs.h" |
| #include "emit-rtl.h" |
| #include "recog.h" |
| #include "diagnostic-core.h" |
| #include "fold-const.h" |
| #include "varasm.h" |
| #include "stor-layout.h" |
| #include "output.h" |
| #include "insn-attr.h" |
| #include "explow.h" |
| #include "expr.h" |
| #include "cfgrtl.h" |
| #include "langhooks.h" |
| #include "dumpfile.h" |
| #include "builtins.h" |
| #include "ifcvt.h" |
| #include "rtl-iter.h" |
| #include "calls.h" |
| |
| /* This file should be included last. */ |
| #include "target-def.h" |
| |
| #ifndef FRV_INLINE |
| #define FRV_INLINE inline |
| #endif |
| |
| /* The maximum number of distinct NOP patterns. There are three: |
| nop, fnop and mnop. */ |
| #define NUM_NOP_PATTERNS 3 |
| |
| /* Classification of instructions and units: integer, floating-point/media, |
| branch and control. */ |
| enum frv_insn_group { GROUP_I, GROUP_FM, GROUP_B, GROUP_C, NUM_GROUPS }; |
| |
| /* The DFA names of the units, in packet order. */ |
| static const char *const frv_unit_names[] = |
| { |
| "c", |
| "i0", "f0", |
| "i1", "f1", |
| "i2", "f2", |
| "i3", "f3", |
| "b0", "b1" |
| }; |
| |
| /* The classification of each unit in frv_unit_names[]. */ |
| static const enum frv_insn_group frv_unit_groups[ARRAY_SIZE (frv_unit_names)] = |
| { |
| GROUP_C, |
| GROUP_I, GROUP_FM, |
| GROUP_I, GROUP_FM, |
| GROUP_I, GROUP_FM, |
| GROUP_I, GROUP_FM, |
| GROUP_B, GROUP_B |
| }; |
| |
| /* Return the DFA unit code associated with the Nth unit of integer |
| or floating-point group GROUP, */ |
| #define NTH_UNIT(GROUP, N) frv_unit_codes[(GROUP) + (N) * 2 + 1] |
| |
| /* Return the number of integer or floating-point unit UNIT |
| (1 for I1, 2 for F2, etc.). */ |
| #define UNIT_NUMBER(UNIT) (((UNIT) - 1) / 2) |
| |
| /* The DFA unit number for each unit in frv_unit_names[]. */ |
| static int frv_unit_codes[ARRAY_SIZE (frv_unit_names)]; |
| |
| /* FRV_TYPE_TO_UNIT[T] is the last unit in frv_unit_names[] that can issue |
| an instruction of type T. The value is ARRAY_SIZE (frv_unit_names) if |
| no instruction of type T has been seen. */ |
| static unsigned int frv_type_to_unit[TYPE_UNKNOWN + 1]; |
| |
| /* An array of dummy nop INSNs, one for each type of nop that the |
| target supports. */ |
| static GTY(()) rtx_insn *frv_nops[NUM_NOP_PATTERNS]; |
| |
| /* The number of nop instructions in frv_nops[]. */ |
| static unsigned int frv_num_nops; |
| |
| /* The type of access. FRV_IO_UNKNOWN means the access can be either |
| a read or a write. */ |
| enum frv_io_type { FRV_IO_UNKNOWN, FRV_IO_READ, FRV_IO_WRITE }; |
| |
| /* Information about one __builtin_read or __builtin_write access, or |
| the combination of several such accesses. The most general value |
| is all-zeros (an unknown access to an unknown address). */ |
| struct frv_io { |
| enum frv_io_type type; |
| |
| /* The constant address being accessed, or zero if not known. */ |
| HOST_WIDE_INT const_address; |
| |
| /* The run-time address, as used in operand 0 of the membar pattern. */ |
| rtx var_address; |
| }; |
| |
| /* Return true if instruction INSN should be packed with the following |
| instruction. */ |
| #define PACKING_FLAG_P(INSN) (GET_MODE (INSN) == TImode) |
| |
| /* Set the value of PACKING_FLAG_P(INSN). */ |
| #define SET_PACKING_FLAG(INSN) PUT_MODE (INSN, TImode) |
| #define CLEAR_PACKING_FLAG(INSN) PUT_MODE (INSN, VOIDmode) |
| |
| /* Loop with REG set to each hard register in rtx X. */ |
| #define FOR_EACH_REGNO(REG, X) \ |
| for (REG = REGNO (X); REG < END_REGNO (X); REG++) |
| |
| /* This structure contains machine specific function data. */ |
| struct GTY(()) machine_function |
| { |
| /* True if we have created an rtx that relies on the stack frame. */ |
| int frame_needed; |
| |
| /* True if this function contains at least one __builtin_{read,write}*. */ |
| bool has_membar_p; |
| }; |
| |
| /* Temporary register allocation support structure. */ |
| typedef struct frv_tmp_reg_struct |
| { |
| HARD_REG_SET regs; /* possible registers to allocate */ |
| int next_reg[N_REG_CLASSES]; /* next register to allocate per class */ |
| } |
| frv_tmp_reg_t; |
| |
| /* Register state information for VLIW re-packing phase. */ |
| #define REGSTATE_CC_MASK 0x07 /* Mask to isolate CCn for cond exec */ |
| #define REGSTATE_MODIFIED 0x08 /* reg modified in current VLIW insn */ |
| #define REGSTATE_IF_TRUE 0x10 /* reg modified in cond exec true */ |
| #define REGSTATE_IF_FALSE 0x20 /* reg modified in cond exec false */ |
| |
| #define REGSTATE_IF_EITHER (REGSTATE_IF_TRUE | REGSTATE_IF_FALSE) |
| |
| typedef unsigned char regstate_t; |
| |
| /* Used in frv_frame_accessor_t to indicate the direction of a register-to- |
| memory move. */ |
| enum frv_stack_op |
| { |
| FRV_LOAD, |
| FRV_STORE |
| }; |
| |
| /* Information required by frv_frame_access. */ |
| typedef struct |
| { |
| /* This field is FRV_LOAD if registers are to be loaded from the stack and |
| FRV_STORE if they should be stored onto the stack. FRV_STORE implies |
| the move is being done by the prologue code while FRV_LOAD implies it |
| is being done by the epilogue. */ |
| enum frv_stack_op op; |
| |
| /* The base register to use when accessing the stack. This may be the |
| frame pointer, stack pointer, or a temporary. The choice of register |
| depends on which part of the frame is being accessed and how big the |
| frame is. */ |
| rtx base; |
| |
| /* The offset of BASE from the bottom of the current frame, in bytes. */ |
| int base_offset; |
| } frv_frame_accessor_t; |
| |
| /* Conditional execution support gathered together in one structure. */ |
| typedef struct |
| { |
| /* Linked list of insns to add if the conditional execution conversion was |
| successful. Each link points to an EXPR_LIST which points to the pattern |
| of the insn to add, and the insn to be inserted before. */ |
| rtx added_insns_list; |
| |
| /* Identify which registers are safe to allocate for if conversions to |
| conditional execution. We keep the last allocated register in the |
| register classes between COND_EXEC statements. This will mean we allocate |
| different registers for each different COND_EXEC group if we can. This |
| might allow the scheduler to intermix two different COND_EXEC sections. */ |
| frv_tmp_reg_t tmp_reg; |
| |
| /* For nested IFs, identify which CC registers are used outside of setting |
| via a compare isnsn, and using via a check insn. This will allow us to |
| know if we can rewrite the register to use a different register that will |
| be paired with the CR register controlling the nested IF-THEN blocks. */ |
| HARD_REG_SET nested_cc_ok_rewrite; |
| |
| /* Temporary registers allocated to hold constants during conditional |
| execution. */ |
| rtx scratch_regs[FIRST_PSEUDO_REGISTER]; |
| |
| /* Current number of temp registers available. */ |
| int cur_scratch_regs; |
| |
| /* Number of nested conditional execution blocks. */ |
| int num_nested_cond_exec; |
| |
| /* Map of insns that set up constants in scratch registers. */ |
| bitmap scratch_insns_bitmap; |
| |
| /* Conditional execution test register (CC0..CC7). */ |
| rtx cr_reg; |
| |
| /* Conditional execution compare register that is paired with cr_reg, so that |
| nested compares can be done. The csubcc and caddcc instructions don't |
| have enough bits to specify both a CC register to be set and a CR register |
| to do the test on, so the same bit number is used for both. Needless to |
| say, this is rather inconvenient for GCC. */ |
| rtx nested_cc_reg; |
| |
| /* Extra CR registers used for &&, ||. */ |
| rtx extra_int_cr; |
| rtx extra_fp_cr; |
| |
| /* Previous CR used in nested if, to make sure we are dealing with the same |
| nested if as the previous statement. */ |
| rtx last_nested_if_cr; |
| } |
| frv_ifcvt_t; |
| |
| static /* GTY(()) */ frv_ifcvt_t frv_ifcvt; |
| |
| /* Map register number to smallest register class. */ |
| enum reg_class regno_reg_class[FIRST_PSEUDO_REGISTER]; |
| |
| /* Cached value of frv_stack_info. */ |
| static frv_stack_t *frv_stack_cache = (frv_stack_t *)0; |
| |
| /* Forward references */ |
| |
| static void frv_option_override (void); |
| static bool frv_legitimate_address_p (machine_mode, rtx, bool); |
| static int frv_default_flags_for_cpu (void); |
| static FRV_INLINE bool frv_small_data_reloc_p (rtx, int); |
| static void frv_print_operand (FILE *, rtx, int); |
| static void frv_print_operand_address (FILE *, machine_mode, rtx); |
| static bool frv_print_operand_punct_valid_p (unsigned char code); |
| static void frv_print_operand_memory_reference_reg |
| (FILE *, rtx); |
| static void frv_print_operand_memory_reference (FILE *, rtx, int); |
| static int frv_print_operand_jump_hint (rtx_insn *); |
| static const char *comparison_string (enum rtx_code, rtx); |
| static rtx frv_function_value (const_tree, const_tree, |
| bool); |
| static rtx frv_libcall_value (machine_mode, |
| const_rtx); |
| static FRV_INLINE int frv_regno_ok_for_base_p (int, int); |
| static rtx single_set_pattern (rtx); |
| static int frv_function_contains_far_jump (void); |
| static rtx frv_alloc_temp_reg (frv_tmp_reg_t *, |
| enum reg_class, |
| machine_mode, |
| int, int); |
| static rtx frv_frame_offset_rtx (int); |
| static rtx frv_frame_mem (machine_mode, rtx, int); |
| static rtx frv_dwarf_store (rtx, int); |
| static void frv_frame_insn (rtx, rtx); |
| static void frv_frame_access (frv_frame_accessor_t*, |
| rtx, int); |
| static void frv_frame_access_multi (frv_frame_accessor_t*, |
| frv_stack_t *, int); |
| static void frv_frame_access_standard_regs (enum frv_stack_op, |
| frv_stack_t *); |
| static struct machine_function *frv_init_machine_status (void); |
| static rtx frv_int_to_acc (enum insn_code, int, rtx); |
| static machine_mode frv_matching_accg_mode (machine_mode); |
| static rtx frv_read_argument (tree, unsigned int); |
| static rtx frv_read_iacc_argument (machine_mode, tree, unsigned int); |
| static int frv_check_constant_argument (enum insn_code, int, rtx); |
| static rtx frv_legitimize_target (enum insn_code, rtx); |
| static rtx frv_legitimize_argument (enum insn_code, int, rtx); |
| static rtx frv_legitimize_tls_address (rtx, enum tls_model); |
| static rtx frv_legitimize_address (rtx, rtx, machine_mode); |
| static rtx frv_expand_set_builtin (enum insn_code, tree, rtx); |
| static rtx frv_expand_unop_builtin (enum insn_code, tree, rtx); |
| static rtx frv_expand_binop_builtin (enum insn_code, tree, rtx); |
| static rtx frv_expand_cut_builtin (enum insn_code, tree, rtx); |
| static rtx frv_expand_binopimm_builtin (enum insn_code, tree, rtx); |
| static rtx frv_expand_voidbinop_builtin (enum insn_code, tree); |
| static rtx frv_expand_int_void2arg (enum insn_code, tree); |
| static rtx frv_expand_prefetches (enum insn_code, tree); |
| static rtx frv_expand_voidtriop_builtin (enum insn_code, tree); |
| static rtx frv_expand_voidaccop_builtin (enum insn_code, tree); |
| static rtx frv_expand_mclracc_builtin (tree); |
| static rtx frv_expand_mrdacc_builtin (enum insn_code, tree); |
| static rtx frv_expand_mwtacc_builtin (enum insn_code, tree); |
| static rtx frv_expand_noargs_builtin (enum insn_code); |
| static void frv_split_iacc_move (rtx, rtx); |
| static rtx frv_emit_comparison (enum rtx_code, rtx, rtx); |
| static void frv_ifcvt_add_insn (rtx, rtx_insn *, int); |
| static rtx frv_ifcvt_rewrite_mem (rtx, machine_mode, rtx); |
| static rtx frv_ifcvt_load_value (rtx, rtx); |
| static unsigned int frv_insn_unit (rtx_insn *); |
| static bool frv_issues_to_branch_unit_p (rtx_insn *); |
| static int frv_cond_flags (rtx); |
| static bool frv_regstate_conflict_p (regstate_t, regstate_t); |
| static bool frv_registers_conflict_p (rtx); |
| static void frv_registers_update_1 (rtx, const_rtx, void *); |
| static void frv_registers_update (rtx); |
| static void frv_start_packet (void); |
| static void frv_start_packet_block (void); |
| static void frv_finish_packet (void (*) (void)); |
| static bool frv_pack_insn_p (rtx_insn *); |
| static void frv_add_insn_to_packet (rtx_insn *); |
| static void frv_insert_nop_in_packet (rtx_insn *); |
| static bool frv_for_each_packet (void (*) (void)); |
| static bool frv_sort_insn_group_1 (enum frv_insn_group, |
| unsigned int, unsigned int, |
| unsigned int, unsigned int, |
| state_t); |
| static int frv_compare_insns (const void *, const void *); |
| static void frv_sort_insn_group (enum frv_insn_group); |
| static void frv_reorder_packet (void); |
| static void frv_fill_unused_units (enum frv_insn_group); |
| static void frv_align_label (void); |
| static void frv_reorg_packet (void); |
| static void frv_register_nop (rtx); |
| static void frv_reorg (void); |
| static void frv_pack_insns (void); |
| static void frv_function_prologue (FILE *); |
| static void frv_function_epilogue (FILE *); |
| static bool frv_assemble_integer (rtx, unsigned, int); |
| static void frv_init_builtins (void); |
| static rtx frv_expand_builtin (tree, rtx, rtx, machine_mode, int); |
| static void frv_init_libfuncs (void); |
| static bool frv_in_small_data_p (const_tree); |
| static void frv_asm_output_mi_thunk |
| (FILE *, tree, HOST_WIDE_INT, HOST_WIDE_INT, tree); |
| static void frv_setup_incoming_varargs (cumulative_args_t, |
| const function_arg_info &, |
| int *, int); |
| static rtx frv_expand_builtin_saveregs (void); |
| static void frv_expand_builtin_va_start (tree, rtx); |
| static bool frv_rtx_costs (rtx, machine_mode, int, int, |
| int*, bool); |
| static int frv_register_move_cost (machine_mode, |
| reg_class_t, reg_class_t); |
| static int frv_memory_move_cost (machine_mode, |
| reg_class_t, bool); |
| static void frv_asm_out_constructor (rtx, int); |
| static void frv_asm_out_destructor (rtx, int); |
| static bool frv_function_symbol_referenced_p (rtx); |
| static bool frv_legitimate_constant_p (machine_mode, rtx); |
| static bool frv_cannot_force_const_mem (machine_mode, rtx); |
| static const char *unspec_got_name (int); |
| static void frv_output_const_unspec (FILE *, |
| const struct frv_unspec *); |
| static bool frv_function_ok_for_sibcall (tree, tree); |
| static rtx frv_struct_value_rtx (tree, int); |
| static bool frv_must_pass_in_stack (const function_arg_info &); |
| static int frv_arg_partial_bytes (cumulative_args_t, |
| const function_arg_info &); |
| static rtx frv_function_arg (cumulative_args_t, const function_arg_info &); |
| static rtx frv_function_incoming_arg (cumulative_args_t, |
| const function_arg_info &); |
| static void frv_function_arg_advance (cumulative_args_t, |
| const function_arg_info &); |
| static unsigned int frv_function_arg_boundary (machine_mode, |
| const_tree); |
| static void frv_output_dwarf_dtprel (FILE *, int, rtx) |
| ATTRIBUTE_UNUSED; |
| static reg_class_t frv_secondary_reload (bool, rtx, reg_class_t, |
| machine_mode, |
| secondary_reload_info *); |
| static bool frv_frame_pointer_required (void); |
| static bool frv_can_eliminate (const int, const int); |
| static void frv_conditional_register_usage (void); |
| static void frv_trampoline_init (rtx, tree, rtx); |
| static bool frv_class_likely_spilled_p (reg_class_t); |
| static unsigned int frv_hard_regno_nregs (unsigned int, machine_mode); |
| static bool frv_hard_regno_mode_ok (unsigned int, machine_mode); |
| static bool frv_modes_tieable_p (machine_mode, machine_mode); |
| |
| /* Initialize the GCC target structure. */ |
| #undef TARGET_PRINT_OPERAND |
| #define TARGET_PRINT_OPERAND frv_print_operand |
| #undef TARGET_PRINT_OPERAND_ADDRESS |
| #define TARGET_PRINT_OPERAND_ADDRESS frv_print_operand_address |
| #undef TARGET_PRINT_OPERAND_PUNCT_VALID_P |
| #define TARGET_PRINT_OPERAND_PUNCT_VALID_P frv_print_operand_punct_valid_p |
| #undef TARGET_ASM_FUNCTION_PROLOGUE |
| #define TARGET_ASM_FUNCTION_PROLOGUE frv_function_prologue |
| #undef TARGET_ASM_FUNCTION_EPILOGUE |
| #define TARGET_ASM_FUNCTION_EPILOGUE frv_function_epilogue |
| #undef TARGET_ASM_INTEGER |
| #define TARGET_ASM_INTEGER frv_assemble_integer |
| #undef TARGET_OPTION_OVERRIDE |
| #define TARGET_OPTION_OVERRIDE frv_option_override |
| #undef TARGET_INIT_BUILTINS |
| #define TARGET_INIT_BUILTINS frv_init_builtins |
| #undef TARGET_EXPAND_BUILTIN |
| #define TARGET_EXPAND_BUILTIN frv_expand_builtin |
| #undef TARGET_INIT_LIBFUNCS |
| #define TARGET_INIT_LIBFUNCS frv_init_libfuncs |
| #undef TARGET_IN_SMALL_DATA_P |
| #define TARGET_IN_SMALL_DATA_P frv_in_small_data_p |
| #undef TARGET_REGISTER_MOVE_COST |
| #define TARGET_REGISTER_MOVE_COST frv_register_move_cost |
| #undef TARGET_MEMORY_MOVE_COST |
| #define TARGET_MEMORY_MOVE_COST frv_memory_move_cost |
| #undef TARGET_RTX_COSTS |
| #define TARGET_RTX_COSTS frv_rtx_costs |
| #undef TARGET_ASM_CONSTRUCTOR |
| #define TARGET_ASM_CONSTRUCTOR frv_asm_out_constructor |
| #undef TARGET_ASM_DESTRUCTOR |
| #define TARGET_ASM_DESTRUCTOR frv_asm_out_destructor |
| |
| #undef TARGET_ASM_OUTPUT_MI_THUNK |
| #define TARGET_ASM_OUTPUT_MI_THUNK frv_asm_output_mi_thunk |
| #undef TARGET_ASM_CAN_OUTPUT_MI_THUNK |
| #define TARGET_ASM_CAN_OUTPUT_MI_THUNK default_can_output_mi_thunk_no_vcall |
| |
| #undef TARGET_SCHED_ISSUE_RATE |
| #define TARGET_SCHED_ISSUE_RATE frv_issue_rate |
| |
| #undef TARGET_LEGITIMIZE_ADDRESS |
| #define TARGET_LEGITIMIZE_ADDRESS frv_legitimize_address |
| |
| #undef TARGET_FUNCTION_OK_FOR_SIBCALL |
| #define TARGET_FUNCTION_OK_FOR_SIBCALL frv_function_ok_for_sibcall |
| #undef TARGET_LEGITIMATE_CONSTANT_P |
| #define TARGET_LEGITIMATE_CONSTANT_P frv_legitimate_constant_p |
| #undef TARGET_CANNOT_FORCE_CONST_MEM |
| #define TARGET_CANNOT_FORCE_CONST_MEM frv_cannot_force_const_mem |
| |
| #undef TARGET_HAVE_TLS |
| #define TARGET_HAVE_TLS HAVE_AS_TLS |
| |
| #undef TARGET_STRUCT_VALUE_RTX |
| #define TARGET_STRUCT_VALUE_RTX frv_struct_value_rtx |
| #undef TARGET_MUST_PASS_IN_STACK |
| #define TARGET_MUST_PASS_IN_STACK frv_must_pass_in_stack |
| #undef TARGET_PASS_BY_REFERENCE |
| #define TARGET_PASS_BY_REFERENCE hook_pass_by_reference_must_pass_in_stack |
| #undef TARGET_ARG_PARTIAL_BYTES |
| #define TARGET_ARG_PARTIAL_BYTES frv_arg_partial_bytes |
| #undef TARGET_FUNCTION_ARG |
| #define TARGET_FUNCTION_ARG frv_function_arg |
| #undef TARGET_FUNCTION_INCOMING_ARG |
| #define TARGET_FUNCTION_INCOMING_ARG frv_function_incoming_arg |
| #undef TARGET_FUNCTION_ARG_ADVANCE |
| #define TARGET_FUNCTION_ARG_ADVANCE frv_function_arg_advance |
| #undef TARGET_FUNCTION_ARG_BOUNDARY |
| #define TARGET_FUNCTION_ARG_BOUNDARY frv_function_arg_boundary |
| |
| #undef TARGET_EXPAND_BUILTIN_SAVEREGS |
| #define TARGET_EXPAND_BUILTIN_SAVEREGS frv_expand_builtin_saveregs |
| #undef TARGET_SETUP_INCOMING_VARARGS |
| #define TARGET_SETUP_INCOMING_VARARGS frv_setup_incoming_varargs |
| #undef TARGET_MACHINE_DEPENDENT_REORG |
| #define TARGET_MACHINE_DEPENDENT_REORG frv_reorg |
| |
| #undef TARGET_EXPAND_BUILTIN_VA_START |
| #define TARGET_EXPAND_BUILTIN_VA_START frv_expand_builtin_va_start |
| |
| #if HAVE_AS_TLS |
| #undef TARGET_ASM_OUTPUT_DWARF_DTPREL |
| #define TARGET_ASM_OUTPUT_DWARF_DTPREL frv_output_dwarf_dtprel |
| #endif |
| |
| #undef TARGET_CLASS_LIKELY_SPILLED_P |
| #define TARGET_CLASS_LIKELY_SPILLED_P frv_class_likely_spilled_p |
| |
| #undef TARGET_SECONDARY_RELOAD |
| #define TARGET_SECONDARY_RELOAD frv_secondary_reload |
| |
| #undef TARGET_LRA_P |
| #define TARGET_LRA_P hook_bool_void_false |
| |
| #undef TARGET_LEGITIMATE_ADDRESS_P |
| #define TARGET_LEGITIMATE_ADDRESS_P frv_legitimate_address_p |
| |
| #undef TARGET_FRAME_POINTER_REQUIRED |
| #define TARGET_FRAME_POINTER_REQUIRED frv_frame_pointer_required |
| |
| #undef TARGET_CAN_ELIMINATE |
| #define TARGET_CAN_ELIMINATE frv_can_eliminate |
| |
| #undef TARGET_CONDITIONAL_REGISTER_USAGE |
| #define TARGET_CONDITIONAL_REGISTER_USAGE frv_conditional_register_usage |
| |
| #undef TARGET_TRAMPOLINE_INIT |
| #define TARGET_TRAMPOLINE_INIT frv_trampoline_init |
| |
| #undef TARGET_FUNCTION_VALUE |
| #define TARGET_FUNCTION_VALUE frv_function_value |
| #undef TARGET_LIBCALL_VALUE |
| #define TARGET_LIBCALL_VALUE frv_libcall_value |
| |
| #undef TARGET_HARD_REGNO_NREGS |
| #define TARGET_HARD_REGNO_NREGS frv_hard_regno_nregs |
| #undef TARGET_HARD_REGNO_MODE_OK |
| #define TARGET_HARD_REGNO_MODE_OK frv_hard_regno_mode_ok |
| #undef TARGET_MODES_TIEABLE_P |
| #define TARGET_MODES_TIEABLE_P frv_modes_tieable_p |
| #undef TARGET_CONSTANT_ALIGNMENT |
| #define TARGET_CONSTANT_ALIGNMENT constant_alignment_word_strings |
| |
| #undef TARGET_HAVE_SPECULATION_SAFE_VALUE |
| #define TARGET_HAVE_SPECULATION_SAFE_VALUE speculation_safe_value_not_needed |
| |
| struct gcc_target targetm = TARGET_INITIALIZER; |
| |
| #define FRV_SYMBOL_REF_TLS_P(RTX) \ |
| (GET_CODE (RTX) == SYMBOL_REF && SYMBOL_REF_TLS_MODEL (RTX) != 0) |
| |
| |
| /* Any function call that satisfies the machine-independent |
| requirements is eligible on FR-V. */ |
| |
| static bool |
| frv_function_ok_for_sibcall (tree decl ATTRIBUTE_UNUSED, |
| tree exp ATTRIBUTE_UNUSED) |
| { |
| return true; |
| } |
| |
| /* Return true if SYMBOL is a small data symbol and relocation RELOC |
| can be used to access it directly in a load or store. */ |
| |
| static FRV_INLINE bool |
| frv_small_data_reloc_p (rtx symbol, int reloc) |
| { |
| return (GET_CODE (symbol) == SYMBOL_REF |
| && SYMBOL_REF_SMALL_P (symbol) |
| && (!TARGET_FDPIC || flag_pic == 1) |
| && (reloc == R_FRV_GOTOFF12 || reloc == R_FRV_GPREL12)); |
| } |
| |
| /* Return true if X is a valid relocation unspec. If it is, fill in UNSPEC |
| appropriately. */ |
| |
| bool |
| frv_const_unspec_p (rtx x, struct frv_unspec *unspec) |
| { |
| if (GET_CODE (x) == CONST) |
| { |
| unspec->offset = 0; |
| x = XEXP (x, 0); |
| if (GET_CODE (x) == PLUS && GET_CODE (XEXP (x, 1)) == CONST_INT) |
| { |
| unspec->offset += INTVAL (XEXP (x, 1)); |
| x = XEXP (x, 0); |
| } |
| if (GET_CODE (x) == UNSPEC && XINT (x, 1) == UNSPEC_GOT) |
| { |
| unspec->symbol = XVECEXP (x, 0, 0); |
| unspec->reloc = INTVAL (XVECEXP (x, 0, 1)); |
| |
| if (unspec->offset == 0) |
| return true; |
| |
| if (frv_small_data_reloc_p (unspec->symbol, unspec->reloc) |
| && unspec->offset > 0 |
| && unspec->offset < g_switch_value) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* Decide whether we can force certain constants to memory. If we |
| decide we can't, the caller should be able to cope with it in |
| another way. |
| |
| We never allow constants to be forced into memory for TARGET_FDPIC. |
| This is necessary for several reasons: |
| |
| 1. Since frv_legitimate_constant_p rejects constant pool addresses, the |
| target-independent code will try to force them into the constant |
| pool, thus leading to infinite recursion. |
| |
| 2. We can never introduce new constant pool references during reload. |
| Any such reference would require use of the pseudo FDPIC register. |
| |
| 3. We can't represent a constant added to a function pointer (which is |
| not the same as a pointer to a function+constant). |
| |
| 4. In many cases, it's more efficient to calculate the constant in-line. */ |
| |
| static bool |
| frv_cannot_force_const_mem (machine_mode mode ATTRIBUTE_UNUSED, |
| rtx x ATTRIBUTE_UNUSED) |
| { |
| return TARGET_FDPIC; |
| } |
| |
| static int |
| frv_default_flags_for_cpu (void) |
| { |
| switch (frv_cpu_type) |
| { |
| case FRV_CPU_GENERIC: |
| return MASK_DEFAULT_FRV; |
| |
| case FRV_CPU_FR550: |
| return MASK_DEFAULT_FR550; |
| |
| case FRV_CPU_FR500: |
| case FRV_CPU_TOMCAT: |
| return MASK_DEFAULT_FR500; |
| |
| case FRV_CPU_FR450: |
| return MASK_DEFAULT_FR450; |
| |
| case FRV_CPU_FR405: |
| case FRV_CPU_FR400: |
| return MASK_DEFAULT_FR400; |
| |
| case FRV_CPU_FR300: |
| case FRV_CPU_SIMPLE: |
| return MASK_DEFAULT_SIMPLE; |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| /* Implement TARGET_OPTION_OVERRIDE. */ |
| |
| static void |
| frv_option_override (void) |
| { |
| int regno; |
| unsigned int i; |
| |
| target_flags |= (frv_default_flags_for_cpu () & ~target_flags_explicit); |
| |
| /* -mlibrary-pic sets -fPIC and -G0 and also suppresses warnings from the |
| linker about linking pic and non-pic code. */ |
| if (TARGET_LIBPIC) |
| { |
| if (!flag_pic) /* -fPIC */ |
| flag_pic = 2; |
| |
| if (!global_options_set.x_g_switch_value) /* -G0 */ |
| { |
| g_switch_value = 0; |
| } |
| } |
| |
| /* A C expression whose value is a register class containing hard |
| register REGNO. In general there is more than one such class; |
| choose a class which is "minimal", meaning that no smaller class |
| also contains the register. */ |
| |
| for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++) |
| { |
| enum reg_class rclass; |
| |
| if (GPR_P (regno)) |
| { |
| int gpr_reg = regno - GPR_FIRST; |
| |
| if (gpr_reg == GR8_REG) |
| rclass = GR8_REGS; |
| |
| else if (gpr_reg == GR9_REG) |
| rclass = GR9_REGS; |
| |
| else if (gpr_reg == GR14_REG) |
| rclass = FDPIC_FPTR_REGS; |
| |
| else if (gpr_reg == FDPIC_REGNO) |
| rclass = FDPIC_REGS; |
| |
| else if ((gpr_reg & 3) == 0) |
| rclass = QUAD_REGS; |
| |
| else if ((gpr_reg & 1) == 0) |
| rclass = EVEN_REGS; |
| |
| else |
| rclass = GPR_REGS; |
| } |
| |
| else if (FPR_P (regno)) |
| { |
| int fpr_reg = regno - GPR_FIRST; |
| if ((fpr_reg & 3) == 0) |
| rclass = QUAD_FPR_REGS; |
| |
| else if ((fpr_reg & 1) == 0) |
| rclass = FEVEN_REGS; |
| |
| else |
| rclass = FPR_REGS; |
| } |
| |
| else if (regno == LR_REGNO) |
| rclass = LR_REG; |
| |
| else if (regno == LCR_REGNO) |
| rclass = LCR_REG; |
| |
| else if (ICC_P (regno)) |
| rclass = ICC_REGS; |
| |
| else if (FCC_P (regno)) |
| rclass = FCC_REGS; |
| |
| else if (ICR_P (regno)) |
| rclass = ICR_REGS; |
| |
| else if (FCR_P (regno)) |
| rclass = FCR_REGS; |
| |
| else if (ACC_P (regno)) |
| { |
| int r = regno - ACC_FIRST; |
| if ((r & 3) == 0) |
| rclass = QUAD_ACC_REGS; |
| else if ((r & 1) == 0) |
| rclass = EVEN_ACC_REGS; |
| else |
| rclass = ACC_REGS; |
| } |
| |
| else if (ACCG_P (regno)) |
| rclass = ACCG_REGS; |
| |
| else |
| rclass = NO_REGS; |
| |
| regno_reg_class[regno] = rclass; |
| } |
| |
| /* Check for small data option */ |
| if (!global_options_set.x_g_switch_value && !TARGET_LIBPIC) |
| g_switch_value = SDATA_DEFAULT_SIZE; |
| |
| /* There is no single unaligned SI op for PIC code. Sometimes we |
| need to use ".4byte" and sometimes we need to use ".picptr". |
| See frv_assemble_integer for details. */ |
| if (flag_pic || TARGET_FDPIC) |
| targetm.asm_out.unaligned_op.si = 0; |
| |
| if ((target_flags_explicit & MASK_LINKED_FP) == 0) |
| target_flags |= MASK_LINKED_FP; |
| |
| if ((target_flags_explicit & MASK_OPTIMIZE_MEMBAR) == 0) |
| target_flags |= MASK_OPTIMIZE_MEMBAR; |
| |
| for (i = 0; i < ARRAY_SIZE (frv_unit_names); i++) |
| frv_unit_codes[i] = get_cpu_unit_code (frv_unit_names[i]); |
| |
| for (i = 0; i < ARRAY_SIZE (frv_type_to_unit); i++) |
| frv_type_to_unit[i] = ARRAY_SIZE (frv_unit_codes); |
| |
| init_machine_status = frv_init_machine_status; |
| } |
| |
| |
| /* Implement TARGET_CONDITIONAL_REGISTER_USAGE. */ |
| |
| static void |
| frv_conditional_register_usage (void) |
| { |
| int i; |
| |
| for (i = GPR_FIRST + NUM_GPRS; i <= GPR_LAST; i++) |
| fixed_regs[i] = call_used_regs[i] = 1; |
| |
| for (i = FPR_FIRST + NUM_FPRS; i <= FPR_LAST; i++) |
| fixed_regs[i] = call_used_regs[i] = 1; |
| |
| /* Reserve the registers used for conditional execution. At present, we need |
| 1 ICC and 1 ICR register. */ |
| fixed_regs[ICC_TEMP] = call_used_regs[ICC_TEMP] = 1; |
| fixed_regs[ICR_TEMP] = call_used_regs[ICR_TEMP] = 1; |
| |
| if (TARGET_FIXED_CC) |
| { |
| fixed_regs[ICC_FIRST] = call_used_regs[ICC_FIRST] = 1; |
| fixed_regs[FCC_FIRST] = call_used_regs[FCC_FIRST] = 1; |
| fixed_regs[ICR_FIRST] = call_used_regs[ICR_FIRST] = 1; |
| fixed_regs[FCR_FIRST] = call_used_regs[FCR_FIRST] = 1; |
| } |
| |
| if (TARGET_FDPIC) |
| fixed_regs[GPR_FIRST + 16] = fixed_regs[GPR_FIRST + 17] = |
| call_used_regs[GPR_FIRST + 16] = call_used_regs[GPR_FIRST + 17] = 0; |
| |
| #if 0 |
| /* If -fpic, SDA_BASE_REG is the PIC register. */ |
| if (g_switch_value == 0 && !flag_pic) |
| fixed_regs[SDA_BASE_REG] = call_used_regs[SDA_BASE_REG] = 0; |
| |
| if (!flag_pic) |
| fixed_regs[PIC_REGNO] = call_used_regs[PIC_REGNO] = 0; |
| #endif |
| } |
| |
| |
| /* |
| * Compute the stack frame layout |
| * |
| * Register setup: |
| * +---------------+-----------------------+-----------------------+ |
| * |Register |type |caller-save/callee-save| |
| * +---------------+-----------------------+-----------------------+ |
| * |GR0 |Zero register | - | |
| * |GR1 |Stack pointer(SP) | - | |
| * |GR2 |Frame pointer(FP) | - | |
| * |GR3 |Hidden parameter | caller save | |
| * |GR4-GR7 | - | caller save | |
| * |GR8-GR13 |Argument register | caller save | |
| * |GR14-GR15 | - | caller save | |
| * |GR16-GR31 | - | callee save | |
| * |GR32-GR47 | - | caller save | |
| * |GR48-GR63 | - | callee save | |
| * |FR0-FR15 | - | caller save | |
| * |FR16-FR31 | - | callee save | |
| * |FR32-FR47 | - | caller save | |
| * |FR48-FR63 | - | callee save | |
| * +---------------+-----------------------+-----------------------+ |
| * |
| * Stack frame setup: |
| * Low |
| * SP-> |-----------------------------------| |
| * | Argument area | |
| * |-----------------------------------| |
| * | Register save area | |
| * |-----------------------------------| |
| * | Local variable save area | |
| * FP-> |-----------------------------------| |
| * | Old FP | |
| * |-----------------------------------| |
| * | Hidden parameter save area | |
| * |-----------------------------------| |
| * | Return address(LR) storage area | |
| * |-----------------------------------| |
| * | Padding for alignment | |
| * |-----------------------------------| |
| * | Register argument area | |
| * OLD SP-> |-----------------------------------| |
| * | Parameter area | |
| * |-----------------------------------| |
| * High |
| * |
| * Argument area/Parameter area: |
| * |
| * When a function is called, this area is used for argument transfer. When |
| * the argument is set up by the caller function, this area is referred to as |
| * the argument area. When the argument is referenced by the callee function, |
| * this area is referred to as the parameter area. The area is allocated when |
| * all arguments cannot be placed on the argument register at the time of |
| * argument transfer. |
| * |
| * Register save area: |
| * |
| * This is a register save area that must be guaranteed for the caller |
| * function. This area is not secured when the register save operation is not |
| * needed. |
| * |
| * Local variable save area: |
| * |
| * This is the area for local variables and temporary variables. |
| * |
| * Old FP: |
| * |
| * This area stores the FP value of the caller function. |
| * |
| * Hidden parameter save area: |
| * |
| * This area stores the start address of the return value storage |
| * area for a struct/union return function. |
| * When a struct/union is used as the return value, the caller |
| * function stores the return value storage area start address in |
| * register GR3 and passes it to the caller function. |
| * The callee function interprets the address stored in the GR3 |
| * as the return value storage area start address. |
| * When register GR3 needs to be saved into memory, the callee |
| * function saves it in the hidden parameter save area. This |
| * area is not secured when the save operation is not needed. |
| * |
| * Return address(LR) storage area: |
| * |
| * This area saves the LR. The LR stores the address of a return to the caller |
| * function for the purpose of function calling. |
| * |
| * Argument register area: |
| * |
| * This area saves the argument register. This area is not secured when the |
| * save operation is not needed. |
| * |
| * Argument: |
| * |
| * Arguments, the count of which equals the count of argument registers (6 |
| * words), are positioned in registers GR8 to GR13 and delivered to the callee |
| * function. When a struct/union return function is called, the return value |
| * area address is stored in register GR3. Arguments not placed in the |
| * argument registers will be stored in the stack argument area for transfer |
| * purposes. When an 8-byte type argument is to be delivered using registers, |
| * it is divided into two and placed in two registers for transfer. When |
| * argument registers must be saved to memory, the callee function secures an |
| * argument register save area in the stack. In this case, a continuous |
| * argument register save area must be established in the parameter area. The |
| * argument register save area must be allocated as needed to cover the size of |
| * the argument register to be saved. If the function has a variable count of |
| * arguments, it saves all argument registers in the argument register save |
| * area. |
| * |
| * Argument Extension Format: |
| * |
| * When an argument is to be stored in the stack, its type is converted to an |
| * extended type in accordance with the individual argument type. The argument |
| * is freed by the caller function after the return from the callee function is |
| * made. |
| * |
| * +-----------------------+---------------+------------------------+ |
| * | Argument Type |Extended Type |Stack Storage Size(byte)| |
| * +-----------------------+---------------+------------------------+ |
| * |char |int | 4 | |
| * |signed char |int | 4 | |
| * |unsigned char |int | 4 | |
| * |[signed] short int |int | 4 | |
| * |unsigned short int |int | 4 | |
| * |[signed] int |No extension | 4 | |
| * |unsigned int |No extension | 4 | |
| * |[signed] long int |No extension | 4 | |
| * |unsigned long int |No extension | 4 | |
| * |[signed] long long int |No extension | 8 | |
| * |unsigned long long int |No extension | 8 | |
| * |float |double | 8 | |
| * |double |No extension | 8 | |
| * |long double |No extension | 8 | |
| * |pointer |No extension | 4 | |
| * |struct/union |- | 4 (*1) | |
| * +-----------------------+---------------+------------------------+ |
| * |
| * When a struct/union is to be delivered as an argument, the caller copies it |
| * to the local variable area and delivers the address of that area. |
| * |
| * Return Value: |
| * |
| * +-------------------------------+----------------------+ |
| * |Return Value Type |Return Value Interface| |
| * +-------------------------------+----------------------+ |
| * |void |None | |
| * |[signed|unsigned] char |GR8 | |
| * |[signed|unsigned] short int |GR8 | |
| * |[signed|unsigned] int |GR8 | |
| * |[signed|unsigned] long int |GR8 | |
| * |pointer |GR8 | |
| * |[signed|unsigned] long long int|GR8 & GR9 | |
| * |float |GR8 | |
| * |double |GR8 & GR9 | |
| * |long double |GR8 & GR9 | |
| * |struct/union |(*1) | |
| * +-------------------------------+----------------------+ |
| * |
| * When a struct/union is used as the return value, the caller function stores |
| * the start address of the return value storage area into GR3 and then passes |
| * it to the callee function. The callee function interprets GR3 as the start |
| * address of the return value storage area. When this address needs to be |
| * saved in memory, the callee function secures the hidden parameter save area |
| * and saves the address in that area. |
| */ |
| |
| frv_stack_t * |
| frv_stack_info (void) |
| { |
| static frv_stack_t info, zero_info; |
| frv_stack_t *info_ptr = &info; |
| tree fndecl = current_function_decl; |
| int varargs_p = 0; |
| tree cur_arg; |
| tree next_arg; |
| int range; |
| int alignment; |
| int offset; |
| |
| /* If we've already calculated the values and reload is complete, |
| just return now. */ |
| if (frv_stack_cache) |
| return frv_stack_cache; |
| |
| /* Zero all fields. */ |
| info = zero_info; |
| |
| /* Set up the register range information. */ |
| info_ptr->regs[STACK_REGS_GPR].name = "gpr"; |
| info_ptr->regs[STACK_REGS_GPR].first = LAST_ARG_REGNUM + 1; |
| info_ptr->regs[STACK_REGS_GPR].last = GPR_LAST; |
| info_ptr->regs[STACK_REGS_GPR].dword_p = TRUE; |
| |
| info_ptr->regs[STACK_REGS_FPR].name = "fpr"; |
| info_ptr->regs[STACK_REGS_FPR].first = FPR_FIRST; |
| info_ptr->regs[STACK_REGS_FPR].last = FPR_LAST; |
| info_ptr->regs[STACK_REGS_FPR].dword_p = TRUE; |
| |
| info_ptr->regs[STACK_REGS_LR].name = "lr"; |
| info_ptr->regs[STACK_REGS_LR].first = LR_REGNO; |
| info_ptr->regs[STACK_REGS_LR].last = LR_REGNO; |
| info_ptr->regs[STACK_REGS_LR].special_p = 1; |
| |
| info_ptr->regs[STACK_REGS_CC].name = "cc"; |
| info_ptr->regs[STACK_REGS_CC].first = CC_FIRST; |
| info_ptr->regs[STACK_REGS_CC].last = CC_LAST; |
| info_ptr->regs[STACK_REGS_CC].field_p = TRUE; |
| |
| info_ptr->regs[STACK_REGS_LCR].name = "lcr"; |
| info_ptr->regs[STACK_REGS_LCR].first = LCR_REGNO; |
| info_ptr->regs[STACK_REGS_LCR].last = LCR_REGNO; |
| |
| info_ptr->regs[STACK_REGS_STDARG].name = "stdarg"; |
| info_ptr->regs[STACK_REGS_STDARG].first = FIRST_ARG_REGNUM; |
| info_ptr->regs[STACK_REGS_STDARG].last = LAST_ARG_REGNUM; |
| info_ptr->regs[STACK_REGS_STDARG].dword_p = 1; |
| info_ptr->regs[STACK_REGS_STDARG].special_p = 1; |
| |
| info_ptr->regs[STACK_REGS_STRUCT].name = "struct"; |
| info_ptr->regs[STACK_REGS_STRUCT].first = FRV_STRUCT_VALUE_REGNUM; |
| info_ptr->regs[STACK_REGS_STRUCT].last = FRV_STRUCT_VALUE_REGNUM; |
| info_ptr->regs[STACK_REGS_STRUCT].special_p = 1; |
| |
| info_ptr->regs[STACK_REGS_FP].name = "fp"; |
| info_ptr->regs[STACK_REGS_FP].first = FRAME_POINTER_REGNUM; |
| info_ptr->regs[STACK_REGS_FP].last = FRAME_POINTER_REGNUM; |
| info_ptr->regs[STACK_REGS_FP].special_p = 1; |
| |
| /* Determine if this is a stdarg function. If so, allocate space to store |
| the 6 arguments. */ |
| if (cfun->stdarg) |
| varargs_p = 1; |
| |
| else |
| { |
| /* Find the last argument, and see if it is __builtin_va_alist. */ |
| for (cur_arg = DECL_ARGUMENTS (fndecl); cur_arg != (tree)0; cur_arg = next_arg) |
| { |
| next_arg = DECL_CHAIN (cur_arg); |
| if (next_arg == (tree)0) |
| { |
| if (DECL_NAME (cur_arg) |
| && !strcmp (IDENTIFIER_POINTER (DECL_NAME (cur_arg)), "__builtin_va_alist")) |
| varargs_p = 1; |
| |
| break; |
| } |
| } |
| } |
| |
| /* Iterate over all of the register ranges. */ |
| for (range = 0; range < STACK_REGS_MAX; range++) |
| { |
| frv_stack_regs_t *reg_ptr = &(info_ptr->regs[range]); |
| int first = reg_ptr->first; |
| int last = reg_ptr->last; |
| int size_1word = 0; |
| int size_2words = 0; |
| int regno; |
| |
| /* Calculate which registers need to be saved & save area size. */ |
| switch (range) |
| { |
| default: |
| for (regno = first; regno <= last; regno++) |
| { |
| if ((df_regs_ever_live_p (regno) |
| && !call_used_or_fixed_reg_p (regno)) |
| || (crtl->calls_eh_return |
| && (regno >= FIRST_EH_REGNUM && regno <= LAST_EH_REGNUM)) |
| || (!TARGET_FDPIC && flag_pic |
| && crtl->uses_pic_offset_table && regno == PIC_REGNO)) |
| { |
| info_ptr->save_p[regno] = REG_SAVE_1WORD; |
| size_1word += UNITS_PER_WORD; |
| } |
| } |
| break; |
| |
| /* Calculate whether we need to create a frame after everything else |
| has been processed. */ |
| case STACK_REGS_FP: |
| break; |
| |
| case STACK_REGS_LR: |
| if (df_regs_ever_live_p (LR_REGNO) |
| || profile_flag |
| /* This is set for __builtin_return_address, etc. */ |
| || cfun->machine->frame_needed |
| || (TARGET_LINKED_FP && frame_pointer_needed) |
| || (!TARGET_FDPIC && flag_pic |
| && crtl->uses_pic_offset_table)) |
| { |
| info_ptr->save_p[LR_REGNO] = REG_SAVE_1WORD; |
| size_1word += UNITS_PER_WORD; |
| } |
| break; |
| |
| case STACK_REGS_STDARG: |
| if (varargs_p) |
| { |
| /* If this is a stdarg function with a non varardic |
| argument split between registers and the stack, |
| adjust the saved registers downward. */ |
| last -= (ADDR_ALIGN (crtl->args.pretend_args_size, UNITS_PER_WORD) |
| / UNITS_PER_WORD); |
| |
| for (regno = first; regno <= last; regno++) |
| { |
| info_ptr->save_p[regno] = REG_SAVE_1WORD; |
| size_1word += UNITS_PER_WORD; |
| } |
| |
| info_ptr->stdarg_size = size_1word; |
| } |
| break; |
| |
| case STACK_REGS_STRUCT: |
| if (cfun->returns_struct) |
| { |
| info_ptr->save_p[FRV_STRUCT_VALUE_REGNUM] = REG_SAVE_1WORD; |
| size_1word += UNITS_PER_WORD; |
| } |
| break; |
| } |
| |
| |
| if (size_1word) |
| { |
| /* If this is a field, it only takes one word. */ |
| if (reg_ptr->field_p) |
| size_1word = UNITS_PER_WORD; |
| |
| /* Determine which register pairs can be saved together. */ |
| else if (reg_ptr->dword_p && TARGET_DWORD) |
| { |
| for (regno = first; regno < last; regno += 2) |
| { |
| if (info_ptr->save_p[regno] && info_ptr->save_p[regno+1]) |
| { |
| size_2words += 2 * UNITS_PER_WORD; |
| size_1word -= 2 * UNITS_PER_WORD; |
| info_ptr->save_p[regno] = REG_SAVE_2WORDS; |
| info_ptr->save_p[regno+1] = REG_SAVE_NO_SAVE; |
| } |
| } |
| } |
| |
| reg_ptr->size_1word = size_1word; |
| reg_ptr->size_2words = size_2words; |
| |
| if (! reg_ptr->special_p) |
| { |
| info_ptr->regs_size_1word += size_1word; |
| info_ptr->regs_size_2words += size_2words; |
| } |
| } |
| } |
| |
| /* Set up the sizes of each field in the frame body, making the sizes |
| of each be divisible by the size of a dword if dword operations might |
| be used, or the size of a word otherwise. */ |
| alignment = (TARGET_DWORD? 2 * UNITS_PER_WORD : UNITS_PER_WORD); |
| |
| info_ptr->parameter_size = ADDR_ALIGN (crtl->outgoing_args_size, alignment); |
| info_ptr->regs_size = ADDR_ALIGN (info_ptr->regs_size_2words |
| + info_ptr->regs_size_1word, |
| alignment); |
| info_ptr->vars_size = ADDR_ALIGN (get_frame_size (), alignment); |
| |
| info_ptr->pretend_size = crtl->args.pretend_args_size; |
| |
| /* Work out the size of the frame, excluding the header. Both the frame |
| body and register parameter area will be dword-aligned. */ |
| info_ptr->total_size |
| = (ADDR_ALIGN (info_ptr->parameter_size |
| + info_ptr->regs_size |
| + info_ptr->vars_size, |
| 2 * UNITS_PER_WORD) |
| + ADDR_ALIGN (info_ptr->pretend_size |
| + info_ptr->stdarg_size, |
| 2 * UNITS_PER_WORD)); |
| |
| /* See if we need to create a frame at all, if so add header area. */ |
| if (info_ptr->total_size > 0 |
| || frame_pointer_needed |
| || info_ptr->regs[STACK_REGS_LR].size_1word > 0 |
| || info_ptr->regs[STACK_REGS_STRUCT].size_1word > 0) |
| { |
| offset = info_ptr->parameter_size; |
| info_ptr->header_size = 4 * UNITS_PER_WORD; |
| info_ptr->total_size += 4 * UNITS_PER_WORD; |
| |
| /* Calculate the offsets to save normal register pairs. */ |
| for (range = 0; range < STACK_REGS_MAX; range++) |
| { |
| frv_stack_regs_t *reg_ptr = &(info_ptr->regs[range]); |
| if (! reg_ptr->special_p) |
| { |
| int first = reg_ptr->first; |
| int last = reg_ptr->last; |
| int regno; |
| |
| for (regno = first; regno <= last; regno++) |
| if (info_ptr->save_p[regno] == REG_SAVE_2WORDS |
| && regno != FRAME_POINTER_REGNUM |
| && (regno < FIRST_ARG_REGNUM |
| || regno > LAST_ARG_REGNUM)) |
| { |
| info_ptr->reg_offset[regno] = offset; |
| offset += 2 * UNITS_PER_WORD; |
| } |
| } |
| } |
| |
| /* Calculate the offsets to save normal single registers. */ |
| for (range = 0; range < STACK_REGS_MAX; range++) |
| { |
| frv_stack_regs_t *reg_ptr = &(info_ptr->regs[range]); |
| if (! reg_ptr->special_p) |
| { |
| int first = reg_ptr->first; |
| int last = reg_ptr->last; |
| int regno; |
| |
| for (regno = first; regno <= last; regno++) |
| if (info_ptr->save_p[regno] == REG_SAVE_1WORD |
| && regno != FRAME_POINTER_REGNUM |
| && (regno < FIRST_ARG_REGNUM |
| || regno > LAST_ARG_REGNUM)) |
| { |
| info_ptr->reg_offset[regno] = offset; |
| offset += UNITS_PER_WORD; |
| } |
| } |
| } |
| |
| /* Calculate the offset to save the local variables at. */ |
| offset = ADDR_ALIGN (offset, alignment); |
| if (info_ptr->vars_size) |
| { |
| info_ptr->vars_offset = offset; |
| offset += info_ptr->vars_size; |
| } |
| |
| /* Align header to a dword-boundary. */ |
| offset = ADDR_ALIGN (offset, 2 * UNITS_PER_WORD); |
| |
| /* Calculate the offsets in the fixed frame. */ |
| info_ptr->save_p[FRAME_POINTER_REGNUM] = REG_SAVE_1WORD; |
| info_ptr->reg_offset[FRAME_POINTER_REGNUM] = offset; |
| info_ptr->regs[STACK_REGS_FP].size_1word = UNITS_PER_WORD; |
| |
| info_ptr->save_p[LR_REGNO] = REG_SAVE_1WORD; |
| info_ptr->reg_offset[LR_REGNO] = offset + 2*UNITS_PER_WORD; |
| info_ptr->regs[STACK_REGS_LR].size_1word = UNITS_PER_WORD; |
| |
| if (cfun->returns_struct) |
| { |
| info_ptr->save_p[FRV_STRUCT_VALUE_REGNUM] = REG_SAVE_1WORD; |
| info_ptr->reg_offset[FRV_STRUCT_VALUE_REGNUM] = offset + UNITS_PER_WORD; |
| info_ptr->regs[STACK_REGS_STRUCT].size_1word = UNITS_PER_WORD; |
| } |
| |
| /* Calculate the offsets to store the arguments passed in registers |
| for stdarg functions. The register pairs are first and the single |
| register if any is last. The register save area starts on a |
| dword-boundary. */ |
| if (info_ptr->stdarg_size) |
| { |
| int first = info_ptr->regs[STACK_REGS_STDARG].first; |
| int last = info_ptr->regs[STACK_REGS_STDARG].last; |
| int regno; |
| |
| /* Skip the header. */ |
| offset += 4 * UNITS_PER_WORD; |
| for (regno = first; regno <= last; regno++) |
| { |
| if (info_ptr->save_p[regno] == REG_SAVE_2WORDS) |
| { |
| info_ptr->reg_offset[regno] = offset; |
| offset += 2 * UNITS_PER_WORD; |
| } |
| else if (info_ptr->save_p[regno] == REG_SAVE_1WORD) |
| { |
| info_ptr->reg_offset[regno] = offset; |
| offset += UNITS_PER_WORD; |
| } |
| } |
| } |
| } |
| |
| if (reload_completed) |
| frv_stack_cache = info_ptr; |
| |
| return info_ptr; |
| } |
| |
| |
| /* Print the information about the frv stack offsets, etc. when debugging. */ |
| |
| void |
| frv_debug_stack (frv_stack_t *info) |
| { |
| int range; |
| |
| if (!info) |
| info = frv_stack_info (); |
| |
| fprintf (stderr, "\nStack information for function %s:\n", |
| ((current_function_decl && DECL_NAME (current_function_decl)) |
| ? IDENTIFIER_POINTER (DECL_NAME (current_function_decl)) |
| : "<unknown>")); |
| |
| fprintf (stderr, "\ttotal_size\t= %6d\n", info->total_size); |
| fprintf (stderr, "\tvars_size\t= %6d\n", info->vars_size); |
| fprintf (stderr, "\tparam_size\t= %6d\n", info->parameter_size); |
| fprintf (stderr, "\tregs_size\t= %6d, 1w = %3d, 2w = %3d\n", |
| info->regs_size, info->regs_size_1word, info->regs_size_2words); |
| |
| fprintf (stderr, "\theader_size\t= %6d\n", info->header_size); |
| fprintf (stderr, "\tpretend_size\t= %6d\n", info->pretend_size); |
| fprintf (stderr, "\tvars_offset\t= %6d\n", info->vars_offset); |
| fprintf (stderr, "\tregs_offset\t= %6d\n", info->regs_offset); |
| |
| for (range = 0; range < STACK_REGS_MAX; range++) |
| { |
| frv_stack_regs_t *regs = &(info->regs[range]); |
| if ((regs->size_1word + regs->size_2words) > 0) |
| { |
| int first = regs->first; |
| int last = regs->last; |
| int regno; |
| |
| fprintf (stderr, "\t%s\tsize\t= %6d, 1w = %3d, 2w = %3d, save =", |
| regs->name, regs->size_1word + regs->size_2words, |
| regs->size_1word, regs->size_2words); |
| |
| for (regno = first; regno <= last; regno++) |
| { |
| if (info->save_p[regno] == REG_SAVE_1WORD) |
| fprintf (stderr, " %s (%d)", reg_names[regno], |
| info->reg_offset[regno]); |
| |
| else if (info->save_p[regno] == REG_SAVE_2WORDS) |
| fprintf (stderr, " %s-%s (%d)", reg_names[regno], |
| reg_names[regno+1], info->reg_offset[regno]); |
| } |
| |
| fputc ('\n', stderr); |
| } |
| } |
| |
| fflush (stderr); |
| } |
| |
| |
| |
| |
| /* Used during final to control the packing of insns. The value is |
| 1 if the current instruction should be packed with the next one, |
| 0 if it shouldn't or -1 if packing is disabled altogether. */ |
| |
| static int frv_insn_packing_flag; |
| |
| /* True if the current function contains a far jump. */ |
| |
| static int |
| frv_function_contains_far_jump (void) |
| { |
| rtx_insn *insn = get_insns (); |
| while (insn != NULL |
| && !(JUMP_P (insn) |
| && get_attr_far_jump (insn) == FAR_JUMP_YES)) |
| insn = NEXT_INSN (insn); |
| return (insn != NULL); |
| } |
| |
| /* For the FRV, this function makes sure that a function with far jumps |
| will return correctly. It also does the VLIW packing. */ |
| |
| static void |
| frv_function_prologue (FILE *file) |
| { |
| /* If no frame was created, check whether the function uses a call |
| instruction to implement a far jump. If so, save the link in gr3 and |
| replace all returns to LR with returns to GR3. GR3 is used because it |
| is call-clobbered, because is not available to the register allocator, |
| and because all functions that take a hidden argument pointer will have |
| a stack frame. */ |
| if (frv_stack_info ()->total_size == 0 && frv_function_contains_far_jump ()) |
| { |
| rtx_insn *insn; |
| |
| /* Just to check that the above comment is true. */ |
| gcc_assert (!df_regs_ever_live_p (GPR_FIRST + 3)); |
| |
| /* Generate the instruction that saves the link register. */ |
| fprintf (file, "\tmovsg lr,gr3\n"); |
| |
| /* Replace the LR with GR3 in *return_internal patterns. The insn |
| will now return using jmpl @(gr3,0) rather than bralr. We cannot |
| simply emit a different assembly directive because bralr and jmpl |
| execute in different units. */ |
| for (insn = get_insns(); insn != NULL; insn = NEXT_INSN (insn)) |
| if (JUMP_P (insn)) |
| { |
| rtx pattern = PATTERN (insn); |
| if (GET_CODE (pattern) == PARALLEL |
| && XVECLEN (pattern, 0) >= 2 |
| && GET_CODE (XVECEXP (pattern, 0, 0)) == RETURN |
| && GET_CODE (XVECEXP (pattern, 0, 1)) == USE) |
| { |
| rtx address = XEXP (XVECEXP (pattern, 0, 1), 0); |
| if (GET_CODE (address) == REG && REGNO (address) == LR_REGNO) |
| SET_REGNO (address, GPR_FIRST + 3); |
| } |
| } |
| } |
| |
| frv_pack_insns (); |
| |
| /* Allow the garbage collector to free the nops created by frv_reorg. */ |
| memset (frv_nops, 0, sizeof (frv_nops)); |
| } |
| |
| |
| /* Return the next available temporary register in a given class. */ |
| |
| static rtx |
| frv_alloc_temp_reg ( |
| frv_tmp_reg_t *info, /* which registers are available */ |
| enum reg_class rclass, /* register class desired */ |
| machine_mode mode, /* mode to allocate register with */ |
| int mark_as_used, /* register not available after allocation */ |
| int no_abort) /* return NULL instead of aborting */ |
| { |
| int regno = info->next_reg[ (int)rclass ]; |
| int orig_regno = regno; |
| HARD_REG_SET *reg_in_class = ®_class_contents[ (int)rclass ]; |
| int i, nr; |
| |
| for (;;) |
| { |
| if (TEST_HARD_REG_BIT (*reg_in_class, regno) |
| && TEST_HARD_REG_BIT (info->regs, regno)) |
| break; |
| |
| if (++regno >= FIRST_PSEUDO_REGISTER) |
| regno = 0; |
| if (regno == orig_regno) |
| { |
| gcc_assert (no_abort); |
| return NULL_RTX; |
| } |
| } |
| |
| nr = hard_regno_nregs (regno, mode); |
| info->next_reg[ (int)rclass ] = regno + nr; |
| |
| if (mark_as_used) |
| for (i = 0; i < nr; i++) |
| CLEAR_HARD_REG_BIT (info->regs, regno+i); |
| |
| return gen_rtx_REG (mode, regno); |
| } |
| |
| |
| /* Return an rtx with the value OFFSET, which will either be a register or a |
| signed 12-bit integer. It can be used as the second operand in an "add" |
| instruction, or as the index in a load or store. |
| |
| The function returns a constant rtx if OFFSET is small enough, otherwise |
| it loads the constant into register OFFSET_REGNO and returns that. */ |
| static rtx |
| frv_frame_offset_rtx (int offset) |
| { |
| rtx offset_rtx = GEN_INT (offset); |
| if (IN_RANGE (offset, -2048, 2047)) |
| return offset_rtx; |
| else |
| { |
| rtx reg_rtx = gen_rtx_REG (SImode, OFFSET_REGNO); |
| if (IN_RANGE (offset, -32768, 32767)) |
| emit_insn (gen_movsi (reg_rtx, offset_rtx)); |
| else |
| { |
| emit_insn (gen_movsi_high (reg_rtx, offset_rtx)); |
| emit_insn (gen_movsi_lo_sum (reg_rtx, offset_rtx)); |
| } |
| return reg_rtx; |
| } |
| } |
| |
| /* Generate (mem:MODE (plus:Pmode BASE (frv_frame_offset OFFSET)))). The |
| prologue and epilogue uses such expressions to access the stack. */ |
| static rtx |
| frv_frame_mem (machine_mode mode, rtx base, int offset) |
| { |
| return gen_rtx_MEM (mode, gen_rtx_PLUS (Pmode, |
| base, |
| frv_frame_offset_rtx (offset))); |
| } |
| |
| /* Generate a frame-related expression: |
| |
| (set REG (mem (plus (sp) (const_int OFFSET)))). |
| |
| Such expressions are used in FRAME_RELATED_EXPR notes for more complex |
| instructions. Marking the expressions as frame-related is superfluous if |
| the note contains just a single set. But if the note contains a PARALLEL |
| or SEQUENCE that has several sets, each set must be individually marked |
| as frame-related. */ |
| static rtx |
| frv_dwarf_store (rtx reg, int offset) |
| { |
| rtx set = gen_rtx_SET (gen_rtx_MEM (GET_MODE (reg), |
| plus_constant (Pmode, stack_pointer_rtx, |
| offset)), |
| reg); |
| RTX_FRAME_RELATED_P (set) = 1; |
| return set; |
| } |
| |
| /* Emit a frame-related instruction whose pattern is PATTERN. The |
| instruction is the last in a sequence that cumulatively performs the |
| operation described by DWARF_PATTERN. The instruction is marked as |
| frame-related and has a REG_FRAME_RELATED_EXPR note containing |
| DWARF_PATTERN. */ |
| static void |
| frv_frame_insn (rtx pattern, rtx dwarf_pattern) |
| { |
| rtx insn = emit_insn (pattern); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| REG_NOTES (insn) = alloc_EXPR_LIST (REG_FRAME_RELATED_EXPR, |
| dwarf_pattern, |
| REG_NOTES (insn)); |
| } |
| |
| /* Emit instructions that transfer REG to or from the memory location (sp + |
| STACK_OFFSET). The register is stored in memory if ACCESSOR->OP is |
| FRV_STORE and loaded if it is FRV_LOAD. Only the prologue uses this |
| function to store registers and only the epilogue uses it to load them. |
| |
| The caller sets up ACCESSOR so that BASE is equal to (sp + BASE_OFFSET). |
| The generated instruction will use BASE as its base register. BASE may |
| simply be the stack pointer, but if several accesses are being made to a |
| region far away from the stack pointer, it may be more efficient to set |
| up a temporary instead. |
| |
| Store instructions will be frame-related and will be annotated with the |
| overall effect of the store. Load instructions will be followed by a |
| (use) to prevent later optimizations from zapping them. |
| |
| The function takes care of the moves to and from SPRs, using TEMP_REGNO |
| as a temporary in such cases. */ |
| static void |
| frv_frame_access (frv_frame_accessor_t *accessor, rtx reg, int stack_offset) |
| { |
| machine_mode mode = GET_MODE (reg); |
| rtx mem = frv_frame_mem (mode, |
| accessor->base, |
| stack_offset - accessor->base_offset); |
| |
| if (accessor->op == FRV_LOAD) |
| { |
| if (SPR_P (REGNO (reg))) |
| { |
| rtx temp = gen_rtx_REG (mode, TEMP_REGNO); |
| emit_insn (gen_rtx_SET (temp, mem)); |
| emit_insn (gen_rtx_SET (reg, temp)); |
| } |
| else |
| { |
| /* We cannot use reg+reg addressing for DImode access. */ |
| if (mode == DImode |
| && GET_CODE (XEXP (mem, 0)) == PLUS |
| && GET_CODE (XEXP (XEXP (mem, 0), 0)) == REG |
| && GET_CODE (XEXP (XEXP (mem, 0), 1)) == REG) |
| { |
| rtx temp = gen_rtx_REG (SImode, TEMP_REGNO); |
| |
| emit_move_insn (temp, |
| gen_rtx_PLUS (SImode, XEXP (XEXP (mem, 0), 0), |
| XEXP (XEXP (mem, 0), 1))); |
| mem = gen_rtx_MEM (DImode, temp); |
| } |
| emit_insn (gen_rtx_SET (reg, mem)); |
| } |
| emit_use (reg); |
| } |
| else |
| { |
| if (SPR_P (REGNO (reg))) |
| { |
| rtx temp = gen_rtx_REG (mode, TEMP_REGNO); |
| emit_insn (gen_rtx_SET (temp, reg)); |
| frv_frame_insn (gen_rtx_SET (mem, temp), |
| frv_dwarf_store (reg, stack_offset)); |
| } |
| else if (mode == DImode) |
| { |
| /* For DImode saves, the dwarf2 version needs to be a SEQUENCE |
| with a separate save for each register. */ |
| rtx reg1 = gen_rtx_REG (SImode, REGNO (reg)); |
| rtx reg2 = gen_rtx_REG (SImode, REGNO (reg) + 1); |
| rtx set1 = frv_dwarf_store (reg1, stack_offset); |
| rtx set2 = frv_dwarf_store (reg2, stack_offset + 4); |
| |
| /* Also we cannot use reg+reg addressing. */ |
| if (GET_CODE (XEXP (mem, 0)) == PLUS |
| && GET_CODE (XEXP (XEXP (mem, 0), 0)) == REG |
| && GET_CODE (XEXP (XEXP (mem, 0), 1)) == REG) |
| { |
| rtx temp = gen_rtx_REG (SImode, TEMP_REGNO); |
| emit_move_insn (temp, |
| gen_rtx_PLUS (SImode, XEXP (XEXP (mem, 0), 0), |
| XEXP (XEXP (mem, 0), 1))); |
| mem = gen_rtx_MEM (DImode, temp); |
| } |
| |
| frv_frame_insn (gen_rtx_SET (mem, reg), |
| gen_rtx_PARALLEL (VOIDmode, |
| gen_rtvec (2, set1, set2))); |
| } |
| else |
| frv_frame_insn (gen_rtx_SET (mem, reg), |
| frv_dwarf_store (reg, stack_offset)); |
| } |
| } |
| |
| /* A function that uses frv_frame_access to transfer a group of registers to |
| or from the stack. ACCESSOR is passed directly to frv_frame_access, INFO |
| is the stack information generated by frv_stack_info, and REG_SET is the |
| number of the register set to transfer. */ |
| static void |
| frv_frame_access_multi (frv_frame_accessor_t *accessor, |
| frv_stack_t *info, |
| int reg_set) |
| { |
| frv_stack_regs_t *regs_info; |
| int regno; |
| |
| regs_info = &info->regs[reg_set]; |
| for (regno = regs_info->first; regno <= regs_info->last; regno++) |
| if (info->save_p[regno]) |
| frv_frame_access (accessor, |
| info->save_p[regno] == REG_SAVE_2WORDS |
| ? gen_rtx_REG (DImode, regno) |
| : gen_rtx_REG (SImode, regno), |
| info->reg_offset[regno]); |
| } |
| |
| /* Save or restore callee-saved registers that are kept outside the frame |
| header. The function saves the registers if OP is FRV_STORE and restores |
| them if OP is FRV_LOAD. INFO is the stack information generated by |
| frv_stack_info. */ |
| static void |
| frv_frame_access_standard_regs (enum frv_stack_op op, frv_stack_t *info) |
| { |
| frv_frame_accessor_t accessor; |
| |
| accessor.op = op; |
| accessor.base = stack_pointer_rtx; |
| accessor.base_offset = 0; |
| frv_frame_access_multi (&accessor, info, STACK_REGS_GPR); |
| frv_frame_access_multi (&accessor, info, STACK_REGS_FPR); |
| frv_frame_access_multi (&accessor, info, STACK_REGS_LCR); |
| } |
| |
| |
| /* Called after register allocation to add any instructions needed for the |
| prologue. Using a prologue insn is favored compared to putting all of the |
| instructions in the TARGET_ASM_FUNCTION_PROLOGUE target hook, since |
| it allows the scheduler to intermix instructions with the saves of |
| the caller saved registers. In some cases, it might be necessary |
| to emit a barrier instruction as the last insn to prevent such |
| scheduling. |
| |
| Also any insns generated here should have RTX_FRAME_RELATED_P(insn) = 1 |
| so that the debug info generation code can handle them properly. */ |
| void |
| frv_expand_prologue (void) |
| { |
| frv_stack_t *info = frv_stack_info (); |
| rtx sp = stack_pointer_rtx; |
| rtx fp = frame_pointer_rtx; |
| frv_frame_accessor_t accessor; |
| |
| if (TARGET_DEBUG_STACK) |
| frv_debug_stack (info); |
| |
| if (flag_stack_usage_info) |
| current_function_static_stack_size = info->total_size; |
| |
| if (info->total_size == 0) |
| return; |
| |
| /* We're interested in three areas of the frame here: |
| |
| A: the register save area |
| B: the old FP |
| C: the header after B |
| |
| If the frame pointer isn't used, we'll have to set up A, B and C |
| using the stack pointer. If the frame pointer is used, we'll access |
| them as follows: |
| |
| A: set up using sp |
| B: set up using sp or a temporary (see below) |
| C: set up using fp |
| |
| We set up B using the stack pointer if the frame is small enough. |
| Otherwise, it's more efficient to copy the old stack pointer into a |
| temporary and use that. |
| |
| Note that it's important to make sure the prologue and epilogue use the |
| same registers to access A and C, since doing otherwise will confuse |
| the aliasing code. */ |
| |
| /* Set up ACCESSOR for accessing region B above. If the frame pointer |
| isn't used, the same method will serve for C. */ |
| accessor.op = FRV_STORE; |
| if (frame_pointer_needed && info->total_size > 2048) |
| { |
| accessor.base = gen_rtx_REG (Pmode, OLD_SP_REGNO); |
| accessor.base_offset = info->total_size; |
| emit_insn (gen_movsi (accessor.base, sp)); |
| } |
| else |
| { |
| accessor.base = stack_pointer_rtx; |
| accessor.base_offset = 0; |
| } |
| |
| /* Allocate the stack space. */ |
| { |
| rtx asm_offset = frv_frame_offset_rtx (-info->total_size); |
| rtx dwarf_offset = GEN_INT (-info->total_size); |
| |
| frv_frame_insn (gen_stack_adjust (sp, sp, asm_offset), |
| gen_rtx_SET (sp, gen_rtx_PLUS (Pmode, sp, dwarf_offset))); |
| } |
| |
| /* If the frame pointer is needed, store the old one at (sp + FP_OFFSET) |
| and point the new one to that location. */ |
| if (frame_pointer_needed) |
| { |
| int fp_offset = info->reg_offset[FRAME_POINTER_REGNUM]; |
| |
| /* ASM_SRC and DWARF_SRC both point to the frame header. ASM_SRC is |
| based on ACCESSOR.BASE but DWARF_SRC is always based on the stack |
| pointer. */ |
| rtx asm_src = plus_constant (Pmode, accessor.base, |
| fp_offset - accessor.base_offset); |
| rtx dwarf_src = plus_constant (Pmode, sp, fp_offset); |
| |
| /* Store the old frame pointer at (sp + FP_OFFSET). */ |
| frv_frame_access (&accessor, fp, fp_offset); |
| |
| /* Set up the new frame pointer. */ |
| frv_frame_insn (gen_rtx_SET (fp, asm_src), |
| gen_rtx_SET (fp, dwarf_src)); |
| |
| /* Access region C from the frame pointer. */ |
| accessor.base = fp; |
| accessor.base_offset = fp_offset; |
| } |
| |
| /* Set up region C. */ |
| frv_frame_access_multi (&accessor, info, STACK_REGS_STRUCT); |
| frv_frame_access_multi (&accessor, info, STACK_REGS_LR); |
| frv_frame_access_multi (&accessor, info, STACK_REGS_STDARG); |
| |
| /* Set up region A. */ |
| frv_frame_access_standard_regs (FRV_STORE, info); |
| |
| /* If this is a varargs/stdarg function, issue a blockage to prevent the |
| scheduler from moving loads before the stores saving the registers. */ |
| if (info->stdarg_size > 0) |
| emit_insn (gen_blockage ()); |
| |
| /* Set up pic register/small data register for this function. */ |
| if (!TARGET_FDPIC && flag_pic && crtl->uses_pic_offset_table) |
| emit_insn (gen_pic_prologue (gen_rtx_REG (Pmode, PIC_REGNO), |
| gen_rtx_REG (Pmode, LR_REGNO), |
| gen_rtx_REG (SImode, OFFSET_REGNO))); |
| } |
| |
| |
| /* Under frv, all of the work is done via frv_expand_epilogue, but |
| this function provides a convenient place to do cleanup. */ |
| |
| static void |
| frv_function_epilogue (FILE *) |
| { |
| frv_stack_cache = (frv_stack_t *)0; |
| |
| /* Zap last used registers for conditional execution. */ |
| memset (&frv_ifcvt.tmp_reg, 0, sizeof (frv_ifcvt.tmp_reg)); |
| |
| /* Release the bitmap of created insns. */ |
| BITMAP_FREE (frv_ifcvt.scratch_insns_bitmap); |
| } |
| |
| |
| /* Called after register allocation to add any instructions needed for the |
| epilogue. Using an epilogue insn is favored compared to putting all of the |
| instructions in the TARGET_ASM_FUNCTION_PROLOGUE target hook, since |
| it allows the scheduler to intermix instructions with the saves of |
| the caller saved registers. In some cases, it might be necessary |
| to emit a barrier instruction as the last insn to prevent such |
| scheduling. */ |
| |
| void |
| frv_expand_epilogue (bool emit_return) |
| { |
| frv_stack_t *info = frv_stack_info (); |
| rtx fp = frame_pointer_rtx; |
| rtx sp = stack_pointer_rtx; |
| rtx return_addr; |
| int fp_offset; |
| |
| fp_offset = info->reg_offset[FRAME_POINTER_REGNUM]; |
| |
| /* Restore the stack pointer to its original value if alloca or the like |
| is used. */ |
| if (! crtl->sp_is_unchanging) |
| emit_insn (gen_addsi3 (sp, fp, frv_frame_offset_rtx (-fp_offset))); |
| |
| /* Restore the callee-saved registers that were used in this function. */ |
| frv_frame_access_standard_regs (FRV_LOAD, info); |
| |
| /* Set RETURN_ADDR to the address we should return to. Set it to NULL if |
| no return instruction should be emitted. */ |
| if (info->save_p[LR_REGNO]) |
| { |
| int lr_offset; |
| rtx mem; |
| |
| /* Use the same method to access the link register's slot as we did in |
| the prologue. In other words, use the frame pointer if available, |
| otherwise use the stack pointer. |
| |
| LR_OFFSET is the offset of the link register's slot from the start |
| of the frame and MEM is a memory rtx for it. */ |
| lr_offset = info->reg_offset[LR_REGNO]; |
| if (frame_pointer_needed) |
| mem = frv_frame_mem (Pmode, fp, lr_offset - fp_offset); |
| else |
| mem = frv_frame_mem (Pmode, sp, lr_offset); |
| |
| /* Load the old link register into a GPR. */ |
| return_addr = gen_rtx_REG (Pmode, TEMP_REGNO); |
| emit_insn (gen_rtx_SET (return_addr, mem)); |
| } |
| else |
| return_addr = gen_rtx_REG (Pmode, LR_REGNO); |
| |
| /* Restore the old frame pointer. Emit a USE afterwards to make sure |
| the load is preserved. */ |
| if (frame_pointer_needed) |
| { |
| emit_insn (gen_rtx_SET (fp, gen_rtx_MEM (Pmode, fp))); |
| emit_use (fp); |
| } |
| |
| /* Deallocate the stack frame. */ |
| if (info->total_size != 0) |
| { |
| rtx offset = frv_frame_offset_rtx (info->total_size); |
| emit_insn (gen_stack_adjust (sp, sp, offset)); |
| } |
| |
| /* If this function uses eh_return, add the final stack adjustment now. */ |
| if (crtl->calls_eh_return) |
| emit_insn (gen_stack_adjust (sp, sp, EH_RETURN_STACKADJ_RTX)); |
| |
| if (emit_return) |
| emit_jump_insn (gen_epilogue_return (return_addr)); |
| else |
| { |
| rtx lr = return_addr; |
| |
| if (REGNO (return_addr) != LR_REGNO) |
| { |
| lr = gen_rtx_REG (Pmode, LR_REGNO); |
| emit_move_insn (lr, return_addr); |
| } |
| |
| emit_use (lr); |
| } |
| } |
| |
| |
| /* Worker function for TARGET_ASM_OUTPUT_MI_THUNK. */ |
| |
| static void |
| frv_asm_output_mi_thunk (FILE *file, |
| tree thunk_fndecl ATTRIBUTE_UNUSED, |
| HOST_WIDE_INT delta, |
| HOST_WIDE_INT vcall_offset ATTRIBUTE_UNUSED, |
| tree function) |
| { |
| const char *fnname = IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (thunk_fndecl)); |
| const char *name_func = XSTR (XEXP (DECL_RTL (function), 0), 0); |
| const char *name_arg0 = reg_names[FIRST_ARG_REGNUM]; |
| const char *name_jmp = reg_names[JUMP_REGNO]; |
| const char *parallel = (frv_issue_rate () > 1 ? ".p" : ""); |
| |
| assemble_start_function (thunk_fndecl, fnname); |
| |
| /* Do the add using an addi if possible. */ |
| if (IN_RANGE (delta, -2048, 2047)) |
| fprintf (file, "\taddi %s,#%d,%s\n", name_arg0, (int) delta, name_arg0); |
| else |
| { |
| const char *const name_add = reg_names[TEMP_REGNO]; |
| fprintf (file, "\tsethi%s #hi(" HOST_WIDE_INT_PRINT_DEC "),%s\n", |
| parallel, delta, name_add); |
| fprintf (file, "\tsetlo #lo(" HOST_WIDE_INT_PRINT_DEC "),%s\n", |
| delta, name_add); |
| fprintf (file, "\tadd %s,%s,%s\n", name_add, name_arg0, name_arg0); |
| } |
| |
| if (TARGET_FDPIC) |
| { |
| const char *name_pic = reg_names[FDPIC_REGNO]; |
| name_jmp = reg_names[FDPIC_FPTR_REGNO]; |
| |
| if (flag_pic != 1) |
| { |
| fprintf (file, "\tsethi%s #gotofffuncdeschi(", parallel); |
| assemble_name (file, name_func); |
| fprintf (file, "),%s\n", name_jmp); |
| |
| fprintf (file, "\tsetlo #gotofffuncdesclo("); |
| assemble_name (file, name_func); |
| fprintf (file, "),%s\n", name_jmp); |
| |
| fprintf (file, "\tldd @(%s,%s), %s\n", name_jmp, name_pic, name_jmp); |
| } |
| else |
| { |
| fprintf (file, "\tlddo @(%s,#gotofffuncdesc12(", name_pic); |
| assemble_name (file, name_func); |
| fprintf (file, "\t)), %s\n", name_jmp); |
| } |
| } |
| else if (!flag_pic) |
| { |
| fprintf (file, "\tsethi%s #hi(", parallel); |
| assemble_name (file, name_func); |
| fprintf (file, "),%s\n", name_jmp); |
| |
| fprintf (file, "\tsetlo #lo("); |
| assemble_name (file, name_func); |
| fprintf (file, "),%s\n", name_jmp); |
| } |
| else |
| { |
| /* Use JUMP_REGNO as a temporary PIC register. */ |
| const char *name_lr = reg_names[LR_REGNO]; |
| const char *name_gppic = name_jmp; |
| const char *name_tmp = reg_names[TEMP_REGNO]; |
| |
| fprintf (file, "\tmovsg %s,%s\n", name_lr, name_tmp); |
| fprintf (file, "\tcall 1f\n"); |
| fprintf (file, "1:\tmovsg %s,%s\n", name_lr, name_gppic); |
| fprintf (file, "\tmovgs %s,%s\n", name_tmp, name_lr); |
| fprintf (file, "\tsethi%s #gprelhi(1b),%s\n", parallel, name_tmp); |
| fprintf (file, "\tsetlo #gprello(1b),%s\n", name_tmp); |
| fprintf (file, "\tsub %s,%s,%s\n", name_gppic, name_tmp, name_gppic); |
| |
| fprintf (file, "\tsethi%s #gprelhi(", parallel); |
| assemble_name (file, name_func); |
| fprintf (file, "),%s\n", name_tmp); |
| |
| fprintf (file, "\tsetlo #gprello("); |
| assemble_name (file, name_func); |
| fprintf (file, "),%s\n", name_tmp); |
| |
| fprintf (file, "\tadd %s,%s,%s\n", name_gppic, name_tmp, name_jmp); |
| } |
| |
| /* Jump to the function address. */ |
| fprintf (file, "\tjmpl @(%s,%s)\n", name_jmp, reg_names[GPR_FIRST+0]); |
| assemble_end_function (thunk_fndecl, fnname); |
| } |
| |
| |
| |
| /* On frv, create a frame whenever we need to create stack. */ |
| |
| static bool |
| frv_frame_pointer_required (void) |
| { |
| /* If we forgoing the usual linkage requirements, we only need |
| a frame pointer if the stack pointer might change. */ |
| if (!TARGET_LINKED_FP) |
| return !crtl->sp_is_unchanging; |
| |
| if (! crtl->is_leaf) |
| return true; |
| |
| if (get_frame_size () != 0) |
| return true; |
| |
| if (cfun->stdarg) |
| return true; |
| |
| if (!crtl->sp_is_unchanging) |
| return true; |
| |
| if (!TARGET_FDPIC && flag_pic && crtl->uses_pic_offset_table) |
| return true; |
| |
| if (profile_flag) |
| return true; |
| |
| if (cfun->machine->frame_needed) |
| return true; |
| |
| return false; |
| } |
| |
| |
| /* Worker function for TARGET_CAN_ELIMINATE. */ |
| |
| bool |
| frv_can_eliminate (const int from, const int to) |
| { |
| return (from == ARG_POINTER_REGNUM && to == STACK_POINTER_REGNUM |
| ? ! frame_pointer_needed |
| : true); |
| } |
| |
| /* This function returns the initial difference between the specified |
| pair of registers. */ |
| |
| /* See frv_stack_info for more details on the frv stack frame. */ |
| |
| int |
| frv_initial_elimination_offset (int from, int to) |
| { |
| frv_stack_t *info = frv_stack_info (); |
| int ret = 0; |
| |
| if (to == STACK_POINTER_REGNUM && from == ARG_POINTER_REGNUM) |
| ret = info->total_size - info->pretend_size; |
| |
| else if (to == STACK_POINTER_REGNUM && from == FRAME_POINTER_REGNUM) |
| ret = info->reg_offset[FRAME_POINTER_REGNUM]; |
| |
| else if (to == FRAME_POINTER_REGNUM && from == ARG_POINTER_REGNUM) |
| ret = (info->total_size |
| - info->reg_offset[FRAME_POINTER_REGNUM] |
| - info->pretend_size); |
| |
| else |
| gcc_unreachable (); |
| |
| if (TARGET_DEBUG_STACK) |
| fprintf (stderr, "Eliminate %s to %s by adding %d\n", |
| reg_names [from], reg_names[to], ret); |
| |
| return ret; |
| } |
| |
| |
| /* Worker function for TARGET_SETUP_INCOMING_VARARGS. */ |
| |
| static void |
| frv_setup_incoming_varargs (cumulative_args_t cum_v, |
| const function_arg_info &arg, |
| int *pretend_size, |
| int second_time) |
| { |
| CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v); |
| |
| if (TARGET_DEBUG_ARG) |
| fprintf (stderr, |
| "setup_vararg: words = %2d, mode = %4s, pretend_size = %d, second_time = %d\n", |
| *cum, GET_MODE_NAME (arg.mode), *pretend_size, second_time); |
| } |
| |
| |
| /* Worker function for TARGET_EXPAND_BUILTIN_SAVEREGS. */ |
| |
| static rtx |
| frv_expand_builtin_saveregs (void) |
| { |
| int offset = UNITS_PER_WORD * FRV_NUM_ARG_REGS; |
| |
| if (TARGET_DEBUG_ARG) |
| fprintf (stderr, "expand_builtin_saveregs: offset from ap = %d\n", |
| offset); |
| |
| return gen_rtx_PLUS (Pmode, virtual_incoming_args_rtx, GEN_INT (- offset)); |
| } |
| |
| |
| /* Expand __builtin_va_start to do the va_start macro. */ |
| |
| static void |
| frv_expand_builtin_va_start (tree valist, rtx nextarg) |
| { |
| tree t; |
| int num = crtl->args.info - FIRST_ARG_REGNUM - FRV_NUM_ARG_REGS; |
| |
| nextarg = gen_rtx_PLUS (Pmode, virtual_incoming_args_rtx, |
| GEN_INT (UNITS_PER_WORD * num)); |
| |
| if (TARGET_DEBUG_ARG) |
| { |
| fprintf (stderr, "va_start: args_info = %d, num = %d\n", |
| crtl->args.info, num); |
| |
| debug_rtx (nextarg); |
| } |
| |
| t = build2 (MODIFY_EXPR, TREE_TYPE (valist), valist, |
| fold_convert (TREE_TYPE (valist), |
| make_tree (sizetype, nextarg))); |
| TREE_SIDE_EFFECTS (t) = 1; |
| |
| expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL); |
| } |
| |
| |
| /* Expand a block move operation, and return 1 if successful. Return 0 |
| if we should let the compiler generate normal code. |
| |
| operands[0] is the destination |
| operands[1] is the source |
| operands[2] is the length |
| operands[3] is the alignment */ |
| |
| /* Maximum number of loads to do before doing the stores */ |
| #ifndef MAX_MOVE_REG |
| #define MAX_MOVE_REG 4 |
| #endif |
| |
| /* Maximum number of total loads to do. */ |
| #ifndef TOTAL_MOVE_REG |
| #define TOTAL_MOVE_REG 8 |
| #endif |
| |
| int |
| frv_expand_block_move (rtx operands[]) |
| { |
| rtx orig_dest = operands[0]; |
| rtx orig_src = operands[1]; |
| rtx bytes_rtx = operands[2]; |
| rtx align_rtx = operands[3]; |
| int constp = (GET_CODE (bytes_rtx) == CONST_INT); |
| int align; |
| int bytes; |
| int offset; |
| int num_reg; |
| int i; |
| rtx src_reg; |
| rtx dest_reg; |
| rtx src_addr; |
| rtx dest_addr; |
| rtx src_mem; |
| rtx dest_mem; |
| rtx tmp_reg; |
| rtx stores[MAX_MOVE_REG]; |
| int move_bytes; |
| machine_mode mode; |
| |
| /* If this is not a fixed size move, just call memcpy. */ |
| if (! constp) |
| return FALSE; |
| |
| /* This should be a fixed size alignment. */ |
| gcc_assert (GET_CODE (align_rtx) == CONST_INT); |
| |
| align = INTVAL (align_rtx); |
| |
| /* Anything to move? */ |
| bytes = INTVAL (bytes_rtx); |
| if (bytes <= 0) |
| return TRUE; |
| |
| /* Don't support real large moves. */ |
| if (bytes > TOTAL_MOVE_REG*align) |
| return FALSE; |
| |
| /* Move the address into scratch registers. */ |
| dest_reg = copy_addr_to_reg (XEXP (orig_dest, 0)); |
| src_reg = copy_addr_to_reg (XEXP (orig_src, 0)); |
| |
| num_reg = offset = 0; |
| for ( ; bytes > 0; (bytes -= move_bytes), (offset += move_bytes)) |
| { |
| /* Calculate the correct offset for src/dest. */ |
| if (offset == 0) |
| { |
| src_addr = src_reg; |
| dest_addr = dest_reg; |
| } |
| else |
| { |
| src_addr = plus_constant (Pmode, src_reg, offset); |
| dest_addr = plus_constant (Pmode, dest_reg, offset); |
| } |
| |
| /* Generate the appropriate load and store, saving the stores |
| for later. */ |
| if (bytes >= 4 && align >= 4) |
| mode = SImode; |
| else if (bytes >= 2 && align >= 2) |
| mode = HImode; |
| else |
| mode = QImode; |
| |
| move_bytes = GET_MODE_SIZE (mode); |
| tmp_reg = gen_reg_rtx (mode); |
| src_mem = change_address (orig_src, mode, src_addr); |
| dest_mem = change_address (orig_dest, mode, dest_addr); |
| emit_insn (gen_rtx_SET (tmp_reg, src_mem)); |
| stores[num_reg++] = gen_rtx_SET (dest_mem, tmp_reg); |
| |
| if (num_reg >= MAX_MOVE_REG) |
| { |
| for (i = 0; i < num_reg; i++) |
| emit_insn (stores[i]); |
| num_reg = 0; |
| } |
| } |
| |
| for (i = 0; i < num_reg; i++) |
| emit_insn (stores[i]); |
| |
| return TRUE; |
| } |
| |
| |
| /* Expand a block clear operation, and return 1 if successful. Return 0 |
| if we should let the compiler generate normal code. |
| |
| operands[0] is the destination |
| operands[1] is the length |
| operands[3] is the alignment */ |
| |
| int |
| frv_expand_block_clear (rtx operands[]) |
| { |
| rtx orig_dest = operands[0]; |
| rtx bytes_rtx = operands[1]; |
| rtx align_rtx = operands[3]; |
| int constp = (GET_CODE (bytes_rtx) == CONST_INT); |
| int align; |
| int bytes; |
| int offset; |
| rtx dest_reg; |
| rtx dest_addr; |
| rtx dest_mem; |
| int clear_bytes; |
| machine_mode mode; |
| |
| /* If this is not a fixed size move, just call memcpy. */ |
| if (! constp) |
| return FALSE; |
| |
| /* This should be a fixed size alignment. */ |
| gcc_assert (GET_CODE (align_rtx) == CONST_INT); |
| |
| align = INTVAL (align_rtx); |
| |
| /* Anything to move? */ |
| bytes = INTVAL (bytes_rtx); |
| if (bytes <= 0) |
| return TRUE; |
| |
| /* Don't support real large clears. */ |
| if (bytes > TOTAL_MOVE_REG*align) |
| return FALSE; |
| |
| /* Move the address into a scratch register. */ |
| dest_reg = copy_addr_to_reg (XEXP (orig_dest, 0)); |
| |
| offset = 0; |
| for ( ; bytes > 0; (bytes -= clear_bytes), (offset += clear_bytes)) |
| { |
| /* Calculate the correct offset for src/dest. */ |
| dest_addr = ((offset == 0) |
| ? dest_reg |
| : plus_constant (Pmode, dest_reg, offset)); |
| |
| /* Generate the appropriate store of gr0. */ |
| if (bytes >= 4 && align >= 4) |
| mode = SImode; |
| else if (bytes >= 2 && align >= 2) |
| mode = HImode; |
| else |
| mode = QImode; |
| |
| clear_bytes = GET_MODE_SIZE (mode); |
| dest_mem = change_address (orig_dest, mode, dest_addr); |
| emit_insn (gen_rtx_SET (dest_mem, const0_rtx)); |
| } |
| |
| return TRUE; |
| } |
| |
| |
| /* The following variable is used to output modifiers of assembler |
| code of the current output insn. */ |
| |
| static rtx *frv_insn_operands; |
| |
| /* The following function is used to add assembler insn code suffix .p |
| if it is necessary. */ |
| |
| const char * |
| frv_asm_output_opcode (FILE *f, const char *ptr) |
| { |
| int c; |
| |
| if (frv_insn_packing_flag <= 0) |
| return ptr; |
| |
| for (; *ptr && *ptr != ' ' && *ptr != '\t';) |
| { |
| c = *ptr++; |
| if (c == '%' && ((*ptr >= 'a' && *ptr <= 'z') |
| || (*ptr >= 'A' && *ptr <= 'Z'))) |
| { |
| int letter = *ptr++; |
| |
| c = atoi (ptr); |
| frv_print_operand (f, frv_insn_operands [c], letter); |
| while ((c = *ptr) >= '0' && c <= '9') |
| ptr++; |
| } |
| else |
| fputc (c, f); |
| } |
| |
| fprintf (f, ".p"); |
| |
| return ptr; |
| } |
| |
| /* Set up the packing bit for the current output insn. Note that this |
| function is not called for asm insns. */ |
| |
| void |
| frv_final_prescan_insn (rtx_insn *insn, rtx *opvec, |
| int noperands ATTRIBUTE_UNUSED) |
| { |
| if (INSN_P (insn)) |
| { |
| if (frv_insn_packing_flag >= 0) |
| { |
| frv_insn_operands = opvec; |
| frv_insn_packing_flag = PACKING_FLAG_P (insn); |
| } |
| else if (recog_memoized (insn) >= 0 |
| && get_attr_acc_group (insn) == ACC_GROUP_ODD) |
| /* Packing optimizations have been disabled, but INSN can only |
| be issued in M1. Insert an mnop in M0. */ |
| fprintf (asm_out_file, "\tmnop.p\n"); |
| } |
| } |
| |
| |
| |
| /* A C expression whose value is RTL representing the address in a stack frame |
| where the pointer to the caller's frame is stored. Assume that FRAMEADDR is |
| an RTL expression for the address of the stack frame itself. |
| |
| If you don't define this macro, the default is to return the value of |
| FRAMEADDR--that is, the stack frame address is also the address of the stack |
| word that points to the previous frame. */ |
| |
| /* The default is correct, but we need to make sure the frame gets created. */ |
| rtx |
| frv_dynamic_chain_address (rtx frame) |
| { |
| cfun->machine->frame_needed = 1; |
| return frame; |
| } |
| |
| |
| /* A C expression whose value is RTL representing the value of the return |
| address for the frame COUNT steps up from the current frame, after the |
| prologue. FRAMEADDR is the frame pointer of the COUNT frame, or the frame |
| pointer of the COUNT - 1 frame if `RETURN_ADDR_IN_PREVIOUS_FRAME' is |
| defined. |
| |
| The value of the expression must always be the correct address when COUNT is |
| zero, but may be `NULL_RTX' if there is not way to determine the return |
| address of other frames. */ |
| |
| rtx |
| frv_return_addr_rtx (int count, rtx frame) |
| { |
| if (count != 0) |
| return const0_rtx; |
| cfun->machine->frame_needed = 1; |
| return gen_rtx_MEM (Pmode, plus_constant (Pmode, frame, 8)); |
| } |
| |
| /* Given a memory reference MEMREF, interpret the referenced memory as |
| an array of MODE values, and return a reference to the element |
| specified by INDEX. Assume that any pre-modification implicit in |
| MEMREF has already happened. |
| |
| MEMREF must be a legitimate operand for modes larger than SImode. |
| frv_legitimate_address_p forbids register+register addresses, which |
| this function cannot handle. */ |
| rtx |
| frv_index_memory (rtx memref, machine_mode mode, int index) |
| { |
| rtx base = XEXP (memref, 0); |
| if (GET_CODE (base) == PRE_MODIFY) |
| base = XEXP (base, 0); |
| return change_address (memref, mode, |
| plus_constant (Pmode, base, |
| index * GET_MODE_SIZE (mode))); |
| } |
| |
| |
| /* Print a memory address as an operand to reference that memory location. */ |
| static void |
| frv_print_operand_address (FILE * stream, machine_mode /* mode */, rtx x) |
| { |
| if (GET_CODE (x) == MEM) |
| x = XEXP (x, 0); |
| |
| switch (GET_CODE (x)) |
| { |
| case REG: |
| fputs (reg_names [ REGNO (x)], stream); |
| return; |
| |
| case CONST_INT: |
| fprintf (stream, "%ld", (long) INTVAL (x)); |
| return; |
| |
| case SYMBOL_REF: |
| assemble_name (stream, XSTR (x, 0)); |
| return; |
| |
| case LABEL_REF: |
| case CONST: |
| output_addr_const (stream, x); |
| return; |
| |
| case PLUS: |
| /* Poorly constructed asm statements can trigger this alternative. |
| See gcc/testsuite/gcc.dg/asm-4.c for an example. */ |
| frv_print_operand_memory_reference (stream, x, 0); |
| return; |
| |
| default: |
| break; |
| } |
| |
| fatal_insn ("bad insn to frv_print_operand_address:", x); |
| } |
| |
| |
| static void |
| frv_print_operand_memory_reference_reg (FILE * stream, rtx x) |
| { |
| int regno = true_regnum (x); |
| if (GPR_P (regno)) |
| fputs (reg_names[regno], stream); |
| else |
| fatal_insn ("bad register to frv_print_operand_memory_reference_reg:", x); |
| } |
| |
| /* Print a memory reference suitable for the ld/st instructions. */ |
| |
| static void |
| frv_print_operand_memory_reference (FILE * stream, rtx x, int addr_offset) |
| { |
| struct frv_unspec unspec; |
| rtx x0 = NULL_RTX; |
| rtx x1 = NULL_RTX; |
| |
| switch (GET_CODE (x)) |
| { |
| case SUBREG: |
| case REG: |
| x0 = x; |
| break; |
| |
| case PRE_MODIFY: /* (pre_modify (reg) (plus (reg) (reg))) */ |
| x0 = XEXP (x, 0); |
| x1 = XEXP (XEXP (x, 1), 1); |
| break; |
| |
| case CONST_INT: |
| x1 = x; |
| break; |
| |
| case PLUS: |
| x0 = XEXP (x, 0); |
| x1 = XEXP (x, 1); |
| if (GET_CODE (x0) == CONST_INT) |
| { |
| x0 = XEXP (x, 1); |
| x1 = XEXP (x, 0); |
| } |
| break; |
| |
| default: |
| fatal_insn ("bad insn to frv_print_operand_memory_reference:", x); |
| break; |
| |
| } |
| |
| if (addr_offset) |
| { |
| if (!x1) |
| x1 = const0_rtx; |
| else if (GET_CODE (x1) != CONST_INT) |
| fatal_insn ("bad insn to frv_print_operand_memory_reference:", x); |
| } |
| |
| fputs ("@(", stream); |
| if (!x0) |
| fputs (reg_names[GPR_R0], stream); |
| else if (GET_CODE (x0) == REG || GET_CODE (x0) == SUBREG) |
| frv_print_operand_memory_reference_reg (stream, x0); |
| else |
| fatal_insn ("bad insn to frv_print_operand_memory_reference:", x); |
| |
| fputs (",", stream); |
| if (!x1) |
| fputs (reg_names [GPR_R0], stream); |
| |
| else |
| { |
| switch (GET_CODE (x1)) |
| { |
| case SUBREG: |
| case REG: |
| frv_print_operand_memory_reference_reg (stream, x1); |
| break; |
| |
| case CONST_INT: |
| fprintf (stream, "%ld", (long) (INTVAL (x1) + addr_offset)); |
| break; |
| |
| case CONST: |
| if (!frv_const_unspec_p (x1, &unspec)) |
| fatal_insn ("bad insn to frv_print_operand_memory_reference:", x1); |
| frv_output_const_unspec (stream, &unspec); |
| break; |
| |
| default: |
| fatal_insn ("bad insn to frv_print_operand_memory_reference:", x); |
| } |
| } |
| |
| fputs (")", stream); |
| } |
| |
| |
| /* Return 2 for likely branches and 0 for non-likely branches */ |
| |
| #define FRV_JUMP_LIKELY 2 |
| #define FRV_JUMP_NOT_LIKELY 0 |
| |
| static int |
| frv_print_operand_jump_hint (rtx_insn *insn) |
| { |
| rtx note; |
| rtx labelref; |
| int ret; |
| enum { UNKNOWN, BACKWARD, FORWARD } jump_type = UNKNOWN; |
| |
| gcc_assert (JUMP_P (insn)); |
| |
| /* Assume any non-conditional jump is likely. */ |
| if (! any_condjump_p (insn)) |
| ret = FRV_JUMP_LIKELY; |
| |
| else |
| { |
| labelref = condjump_label (insn); |
| if (labelref) |
| { |
| rtx label = XEXP (labelref, 0); |
| jump_type = (insn_current_address > INSN_ADDRESSES (INSN_UID (label)) |
| ? BACKWARD |
| : FORWARD); |
| } |
| |
| note = find_reg_note (insn, REG_BR_PROB, 0); |
| if (!note) |
| ret = ((jump_type == BACKWARD) ? FRV_JUMP_LIKELY : FRV_JUMP_NOT_LIKELY); |
| |
| else |
| { |
| ret = ((profile_probability::from_reg_br_prob_note (XINT (note, 0)) |
| >= profile_probability::even ()) |
| ? FRV_JUMP_LIKELY |
| : FRV_JUMP_NOT_LIKELY); |
| } |
| } |
| |
| #if 0 |
| if (TARGET_DEBUG) |
| { |
| char *direction; |
| |
| switch (jump_type) |
| { |
| default: |
| case UNKNOWN: direction = "unknown jump direction"; break; |
| case BACKWARD: direction = "jump backward"; break; |
| case FORWARD: direction = "jump forward"; break; |
| } |
| |
| fprintf (stderr, |
| "%s: uid %ld, %s, probability = %d, max prob. = %d, hint = %d\n", |
| IDENTIFIER_POINTER (DECL_NAME (current_function_decl)), |
| (long)INSN_UID (insn), direction, prob, |
| REG_BR_PROB_BASE, ret); |
| } |
| #endif |
| |
| return ret; |
| } |
| |
| |
| /* Return the comparison operator to use for CODE given that the ICC |
| register is OP0. */ |
| |
| static const char * |
| comparison_string (enum rtx_code code, rtx op0) |
| { |
| bool is_nz_p = GET_MODE (op0) == CC_NZmode; |
| switch (code) |
| { |
| default: output_operand_lossage ("bad condition code"); return ""; |
| case EQ: return "eq"; |
| case NE: return "ne"; |
| case LT: return is_nz_p ? "n" : "lt"; |
| case LE: return "le"; |
| case GT: return "gt"; |
| case GE: return is_nz_p ? "p" : "ge"; |
| case LTU: return is_nz_p ? "no" : "c"; |
| case LEU: return is_nz_p ? "eq" : "ls"; |
| case GTU: return is_nz_p ? "ne" : "hi"; |
| case GEU: return is_nz_p ? "ra" : "nc"; |
| } |
| } |
| |
| /* Print an operand to an assembler instruction. |
| |
| `%' followed by a letter and a digit says to output an operand in an |
| alternate fashion. Four letters have standard, built-in meanings |
| described below. The hook `TARGET_PRINT_OPERAND' can define |
| additional letters with nonstandard meanings. |
| |
| `%cDIGIT' can be used to substitute an operand that is a constant value |
| without the syntax that normally indicates an immediate operand. |
| |
| `%nDIGIT' is like `%cDIGIT' except that the value of the constant is negated |
| before printing. |
| |
| `%aDIGIT' can be used to substitute an operand as if it were a memory |
| reference, with the actual operand treated as the address. This may be |
| useful when outputting a "load address" instruction, because often the |
| assembler syntax for such an instruction requires you to write the operand |
| as if it were a memory reference. |
| |
| `%lDIGIT' is used to substitute a `label_ref' into a jump instruction. |
| |
| `%=' outputs a number which is unique to each instruction in the entire |
| compilation. This is useful for making local labels to be referred to more |
| than once in a single template that generates multiple assembler |
| instructions. |
| |
| `%' followed by a punctuation character specifies a substitution that |
| does not use an operand. Only one case is standard: `%%' outputs a |
| `%' into the assembler code. Other nonstandard cases can be defined |
| in the `TARGET_PRINT_OPERAND' hook. You must also define which |
| punctuation characters are valid with the |
| `TARGET_PRINT_OPERAND_PUNCT_VALID_P' hook. */ |
| |
| static void |
| frv_print_operand (FILE * file, rtx x, int code) |
| { |
| struct frv_unspec unspec; |
| HOST_WIDE_INT value; |
| int offset; |
| |
| if (code != 0 && !ISALPHA (code)) |
| value = 0; |
| |
| else if (GET_CODE (x) == CONST_INT) |
| value = INTVAL (x); |
| |
| else if (GET_CODE (x) == CONST_DOUBLE) |
| { |
| if (GET_MODE (x) == SFmode) |
| { |
| long l; |
| |
| REAL_VALUE_TO_TARGET_SINGLE (*CONST_DOUBLE_REAL_VALUE (x), l); |
| value = l; |
| } |
| |
| else if (GET_MODE (x) == VOIDmode) |
| value = CONST_DOUBLE_LOW (x); |
| |
| else |
| fatal_insn ("bad insn in frv_print_operand, bad const_double", x); |
| } |
| |
| else |
| value = 0; |
| |
| switch (code) |
| { |
| |
| case '.': |
| /* Output r0. */ |
| fputs (reg_names[GPR_R0], file); |
| break; |
| |
| case '#': |
| fprintf (file, "%d", frv_print_operand_jump_hint (current_output_insn)); |
| break; |
| |
| case '@': |
| /* Output small data area base register (gr16). */ |
| fputs (reg_names[SDA_BASE_REG], file); |
| break; |
| |
| case '~': |
| /* Output pic register (gr17). */ |
| fputs (reg_names[PIC_REGNO], file); |
| break; |
| |
| case '*': |
| /* Output the temporary integer CCR register. */ |
| fputs (reg_names[ICR_TEMP], file); |
| break; |
| |
| case '&': |
| /* Output the temporary integer CC register. */ |
| fputs (reg_names[ICC_TEMP], file); |
| break; |
| |
| /* case 'a': print an address. */ |
| |
| case 'C': |
| /* Print appropriate test for integer branch false operation. */ |
| fputs (comparison_string (reverse_condition (GET_CODE (x)), |
| XEXP (x, 0)), file); |
| break; |
| |
| case 'c': |
| /* Print appropriate test for integer branch true operation. */ |
| fputs (comparison_string (GET_CODE (x), XEXP (x, 0)), file); |
| break; |
| |
| case 'e': |
| /* Print 1 for a NE and 0 for an EQ to give the final argument |
| for a conditional instruction. */ |
| if (GET_CODE (x) == NE) |
| fputs ("1", file); |
| |
| else if (GET_CODE (x) == EQ) |
| fputs ("0", file); |
| |
| else |
| fatal_insn ("bad insn to frv_print_operand, 'e' modifier:", x); |
| break; |
| |
| case 'F': |
| /* Print appropriate test for floating point branch false operation. */ |
| switch (GET_CODE (x)) |
| { |
| default: |
| fatal_insn ("bad insn to frv_print_operand, 'F' modifier:", x); |
| |
| case EQ: fputs ("ne", file); break; |
| case NE: fputs ("eq", file); break; |
| case LT: fputs ("uge", file); break; |
| case LE: fputs ("ug", file); break; |
| case GT: fputs ("ule", file); break; |
| case GE: fputs ("ul", file); break; |
| } |
| break; |
| |
| case 'f': |
| /* Print appropriate test for floating point branch true operation. */ |
| switch (GET_CODE (x)) |
| { |
| default: |
| fatal_insn ("bad insn to frv_print_operand, 'f' modifier:", x); |
| |
| case EQ: fputs ("eq", file); break; |
| case NE: fputs ("ne", file); break; |
| case LT: fputs ("lt", file); break; |
| case LE: fputs ("le", file); break; |
| case GT: fputs ("gt", file); break; |
| case GE: fputs ("ge", file); break; |
| } |
| break; |
| |
| case 'g': |
| /* Print appropriate GOT function. */ |
| if (GET_CODE (x) != CONST_INT) |
| fatal_insn ("bad insn to frv_print_operand, 'g' modifier:", x); |
| fputs (unspec_got_name (INTVAL (x)), file); |
| break; |
| |
| case 'I': |
| /* Print 'i' if the operand is a constant, or is a memory reference that |
| adds a constant. */ |
| if (GET_CODE (x) == MEM) |
| x = ((GET_CODE (XEXP (x, 0)) == PLUS) |
| ? XEXP (XEXP (x, 0), 1) |
| : XEXP (x, 0)); |
| else if (GET_CODE (x) == PLUS) |
| x = XEXP (x, 1); |
| |
| switch (GET_CODE (x)) |
| { |
| default: |
| break; |
| |
| case CONST_INT: |
| case SYMBOL_REF: |
| case CONST: |
| fputs ("i", file); |
| break; |
| } |
| break; |
| |
| case 'i': |
| /* For jump instructions, print 'i' if the operand is a constant or |
| is an expression that adds a constant. */ |
| if (GET_CODE (x) == CONST_INT) |
| fputs ("i", file); |
| |
| else |
| { |
| if (GET_CODE (x) == CONST_INT |
| || (GET_CODE (x) == PLUS |
| && (GET_CODE (XEXP (x, 1)) == CONST_INT |
| || GET_CODE (XEXP (x, 0)) == CONST_INT))) |
| fputs ("i", file); |
| } |
| break; |
| |
| case 'L': |
| /* Print the lower register of a double word register pair */ |
| if (GET_CODE (x) == REG) |
| fputs (reg_names[ REGNO (x)+1 ], file); |
| else |
| fatal_insn ("bad insn to frv_print_operand, 'L' modifier:", x); |
| break; |
| |
| /* case 'l': print a LABEL_REF. */ |
| |
| case 'M': |
| case 'N': |
| /* Print a memory reference for ld/st/jmp, %N prints a memory reference |
| for the second word of double memory operations. */ |
| offset = (code == 'M') ? 0 : UNITS_PER_WORD; |
| switch (GET_CODE (x)) |
| { |
| default: |
| fatal_insn ("bad insn to frv_print_operand, 'M/N' modifier:", x); |
| |
| case MEM: |
| frv_print_operand_memory_reference (file, XEXP (x, 0), offset); |
| break; |
| |
| case REG: |
| case SUBREG: |
| case CONST_INT: |
| case PLUS: |
| case SYMBOL_REF: |
| frv_print_operand_memory_reference (file, x, offset); |
| break; |
| } |
| break; |
| |
| case 'O': |
| /* Print the opcode of a command. */ |
| switch (GET_CODE (x)) |
| { |
| default: |
| fatal_insn ("bad insn to frv_print_operand, 'O' modifier:", x); |
| |
| case PLUS: fputs ("add", file); break; |
| case MINUS: fputs ("sub", file); break; |
| case AND: fputs ("and", file); break; |
| case IOR: fputs ("or", file); break; |
| case XOR: fputs ("xor", file); break; |
| case ASHIFT: fputs ("sll", file); break; |
| case ASHIFTRT: fputs ("sra", file); break; |
| case LSHIFTRT: fputs ("srl", file); break; |
| } |
| break; |
| |
| /* case 'n': negate and print a constant int. */ |
| |
| case 'P': |
| /* Print PIC label using operand as the number. */ |
| if (GET_CODE (x) != CONST_INT) |
| fatal_insn ("bad insn to frv_print_operand, P modifier:", x); |
| |
| fprintf (file, ".LCF%ld", (long)INTVAL (x)); |
| break; |
| |
| case 'U': |
| /* Print 'u' if the operand is a update load/store. */ |
| if (GET_CODE (x) == MEM && GET_CODE (XEXP (x, 0)) == PRE_MODIFY) |
| fputs ("u", file); |
| break; |
| |
| case 'z': |
| /* If value is 0, print gr0, otherwise it must be a register. */ |
| if (GET_CODE (x) == CONST_INT && INTVAL (x) == 0) |
| fputs (reg_names[GPR_R0], file); |
| |
| else if (GET_CODE (x) == REG) |
| fputs (reg_names [REGNO (x)], file); |
| |
| else |
| fatal_insn ("bad insn in frv_print_operand, z case", x); |
| break; |
| |
| case 'x': |
| /* Print constant in hex. */ |
| if (GET_CODE (x) == CONST_INT || GET_CODE (x) == CONST_DOUBLE) |
| { |
| fprintf (file, "%s0x%.4lx", IMMEDIATE_PREFIX, (long) value); |
| break; |
| } |
| |
| /* Fall through. */ |
| |
| case '\0': |
| if (GET_CODE (x) == REG) |
| fputs (reg_names [REGNO (x)], file); |
| |
| else if (GET_CODE (x) == CONST_INT |
| || GET_CODE (x) == CONST_DOUBLE) |
| fprintf (file, "%s%ld", IMMEDIATE_PREFIX, (long) value); |
| |
| else if (frv_const_unspec_p (x, &unspec)) |
| frv_output_const_unspec (file, &unspec); |
| |
| else if (GET_CODE (x) == MEM) |
| frv_print_operand_address (file, GET_MODE (x), XEXP (x, 0)); |
| |
| else if (CONSTANT_ADDRESS_P (x)) |
| frv_print_operand_address (file, VOIDmode, x); |
| |
| else |
| fatal_insn ("bad insn in frv_print_operand, 0 case", x); |
| |
| break; |
| |
| default: |
| fatal_insn ("frv_print_operand: unknown code", x); |
| break; |
| } |
| |
| return; |
| } |
| |
| static bool |
| frv_print_operand_punct_valid_p (unsigned char code) |
| { |
| return (code == '.' || code == '#' || code == '@' || code == '~' |
| || code == '*' || code == '&'); |
| } |
| |
| |
| /* A C statement (sans semicolon) for initializing the variable CUM for the |
| state at the beginning of the argument list. The variable has type |
| `CUMULATIVE_ARGS'. The value of FNTYPE is the tree node for the data type |
| of the function which will receive the args, or 0 if the args are to a |
| compiler support library function. The value of INDIRECT is nonzero when |
| processing an indirect call, for example a call through a function pointer. |
| The value of INDIRECT is zero for a call to an explicitly named function, a |
| library function call, or when `INIT_CUMULATIVE_ARGS' is used to find |
| arguments for the function being compiled. |
| |
| When processing a call to a compiler support library function, LIBNAME |
| identifies which one. It is a `symbol_ref' rtx which contains the name of |
| the function, as a string. LIBNAME is 0 when an ordinary C function call is |
| being processed. Thus, each time this macro is called, either LIBNAME or |
| FNTYPE is nonzero, but never both of them at once. */ |
| |
| void |
| frv_init_cumulative_args (CUMULATIVE_ARGS *cum, |
| tree fntype, |
| rtx libname, |
| tree fndecl, |
| int incoming) |
| { |
| *cum = FIRST_ARG_REGNUM; |
| |
| if (TARGET_DEBUG_ARG) |
| { |
| fprintf (stderr, "\ninit_cumulative_args:"); |
| if (!fndecl && fntype) |
| fputs (" indirect", stderr); |
| |
| if (incoming) |
| fputs (" incoming", stderr); |
| |
| if (fntype) |
| { |
| tree ret_type = TREE_TYPE (fntype); |
| fprintf (stderr, " return=%s,", |
| get_tree_code_name (TREE_CODE (ret_type))); |
| } |
| |
| if (libname && GET_CODE (libname) == SYMBOL_REF) |
| fprintf (stderr, " libname=%s", XSTR (libname, 0)); |
| |
| if (cfun->returns_struct) |
| fprintf (stderr, " return-struct"); |
| |
| putc ('\n', stderr); |
| } |
| } |
| |
| |
| /* Return true if we should pass an argument on the stack rather than |
| in registers. */ |
| |
| static bool |
| frv_must_pass_in_stack (const function_arg_info &arg) |
| { |
| return arg.mode == BLKmode || arg.aggregate_type_p (); |
| } |
| |
| /* If defined, a C expression that gives the alignment boundary, in bits, of an |
| argument with the specified mode and type. If it is not defined, |
| `PARM_BOUNDARY' is used for all arguments. */ |
| |
| static unsigned int |
| frv_function_arg_boundary (machine_mode mode ATTRIBUTE_UNUSED, |
| const_tree type ATTRIBUTE_UNUSED) |
| { |
| return BITS_PER_WORD; |
| } |
| |
| static rtx |
| frv_function_arg_1 (cumulative_args_t cum_v, const function_arg_info &arg, |
| bool incoming ATTRIBUTE_UNUSED) |
| { |
| const CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v); |
| |
| machine_mode xmode = (arg.mode == BLKmode) ? SImode : arg.mode; |
| int arg_num = *cum; |
| rtx ret; |
| const char *debstr; |
| |
| /* Return a marker for use in the call instruction. */ |
| if (xmode == VOIDmode) |
| { |
| ret = const0_rtx; |
| debstr = "<0>"; |
| } |
| |
| else if (arg_num <= LAST_ARG_REGNUM) |
| { |
| ret = gen_rtx_REG (xmode, arg_num); |
| debstr = reg_names[arg_num]; |
| } |
| |
| else |
| { |
| ret = NULL_RTX; |
| debstr = "memory"; |
| } |
| |
| if (TARGET_DEBUG_ARG) |
| fprintf (stderr, |
| "function_arg: words = %2d, mode = %4s, named = %d, size = %3d, arg = %s\n", |
| arg_num, GET_MODE_NAME (arg.mode), arg.named, |
| GET_MODE_SIZE (arg.mode), debstr); |
| |
| return ret; |
| } |
| |
| static rtx |
| frv_function_arg (cumulative_args_t cum, const function_arg_info &arg) |
| { |
| return frv_function_arg_1 (cum, arg, false); |
| } |
| |
| static rtx |
| frv_function_incoming_arg (cumulative_args_t cum, const function_arg_info &arg) |
| { |
| return frv_function_arg_1 (cum, arg, true); |
| } |
| |
| |
| /* Implement TARGET_FUNCTION_ARG_ADVANCE. */ |
| |
| static void |
| frv_function_arg_advance (cumulative_args_t cum_v, |
| const function_arg_info &arg) |
| { |
| CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v); |
| |
| machine_mode xmode = (arg.mode == BLKmode) ? SImode : arg.mode; |
| int bytes = GET_MODE_SIZE (xmode); |
| int words = (bytes + UNITS_PER_WORD - 1) / UNITS_PER_WORD; |
| int arg_num = *cum; |
| |
| *cum = arg_num + words; |
| |
| if (TARGET_DEBUG_ARG) |
| fprintf (stderr, |
| "function_adv: words = %2d, mode = %4s, named = %d, size = %3d\n", |
| arg_num, GET_MODE_NAME (arg.mode), arg.named, |
| words * UNITS_PER_WORD); |
| } |
| |
| |
| /* Implement TARGET_ARG_PARTIAL_BYTES. */ |
| |
| static int |
| frv_arg_partial_bytes (cumulative_args_t cum, const function_arg_info &arg) |
| { |
| machine_mode xmode = (arg.mode == BLKmode) ? SImode : arg.mode; |
| int bytes = GET_MODE_SIZE (xmode); |
| int words = (bytes + UNITS_PER_WORD - 1) / UNITS_PER_WORD; |
| int arg_num = *get_cumulative_args (cum); |
| int ret; |
| |
| ret = ((arg_num <= LAST_ARG_REGNUM && arg_num + words > LAST_ARG_REGNUM+1) |
| ? LAST_ARG_REGNUM - arg_num + 1 |
| : 0); |
| ret *= UNITS_PER_WORD; |
| |
| if (TARGET_DEBUG_ARG && ret) |
| fprintf (stderr, "frv_arg_partial_bytes: %d\n", ret); |
| |
| return ret; |
| } |
| |
| |
| /* Implements TARGET_FUNCTION_VALUE. */ |
| |
| static rtx |
| frv_function_value (const_tree valtype, |
| const_tree fn_decl_or_type ATTRIBUTE_UNUSED, |
| bool outgoing ATTRIBUTE_UNUSED) |
| { |
| return gen_rtx_REG (TYPE_MODE (valtype), RETURN_VALUE_REGNUM); |
| } |
| |
| |
| /* Implements TARGET_LIBCALL_VALUE. */ |
| |
| static rtx |
| frv_libcall_value (machine_mode mode, |
| const_rtx fun ATTRIBUTE_UNUSED) |
| { |
| return gen_rtx_REG (mode, RETURN_VALUE_REGNUM); |
| } |
| |
| |
| /* Implements FUNCTION_VALUE_REGNO_P. */ |
| |
| bool |
| frv_function_value_regno_p (const unsigned int regno) |
| { |
| return (regno == RETURN_VALUE_REGNUM); |
| } |
| |
| /* Return true if a register is ok to use as a base or index register. */ |
| |
| static FRV_INLINE int |
| frv_regno_ok_for_base_p (int regno, int strict_p) |
| { |
| if (GPR_P (regno)) |
| return TRUE; |
| |
| if (strict_p) |
| return (reg_renumber[regno] >= 0 && GPR_P (reg_renumber[regno])); |
| |
| if (regno == ARG_POINTER_REGNUM) |
| |