| /* Subroutines used for MIPS code generation. |
| Copyright (C) 1989, 1990, 1991, 1993, 1994, 1995, 1996, 1997, 1998, |
| 1999, 2000, 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc. |
| Contributed by A. Lichnewsky, lich@inria.inria.fr. |
| Changes by Michael Meissner, meissner@osf.org. |
| 64 bit r4000 support by Ian Lance Taylor, ian@cygnus.com, and |
| Brendan Eich, brendan@microunity.com. |
| |
| This file is part of GCC. |
| |
| GCC is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, 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 COPYING. If not, write to |
| the Free Software Foundation, 59 Temple Place - Suite 330, |
| Boston, MA 02111-1307, USA. */ |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "tm.h" |
| #include <signal.h> |
| #include "rtl.h" |
| #include "regs.h" |
| #include "hard-reg-set.h" |
| #include "real.h" |
| #include "insn-config.h" |
| #include "conditions.h" |
| #include "insn-attr.h" |
| #include "recog.h" |
| #include "toplev.h" |
| #include "output.h" |
| #include "tree.h" |
| #include "function.h" |
| #include "expr.h" |
| #include "optabs.h" |
| #include "flags.h" |
| #include "reload.h" |
| #include "tm_p.h" |
| #include "ggc.h" |
| #include "gstab.h" |
| #include "hashtab.h" |
| #include "debug.h" |
| #include "target.h" |
| #include "target-def.h" |
| #include "integrate.h" |
| #include "langhooks.h" |
| #include "cfglayout.h" |
| #include "sched-int.h" |
| #include "tree-gimple.h" |
| |
| /* True if X is an unspec wrapper around a SYMBOL_REF or LABEL_REF. */ |
| #define UNSPEC_ADDRESS_P(X) \ |
| (GET_CODE (X) == UNSPEC \ |
| && XINT (X, 1) >= UNSPEC_ADDRESS_FIRST \ |
| && XINT (X, 1) < UNSPEC_ADDRESS_FIRST + NUM_SYMBOL_TYPES) |
| |
| /* Extract the symbol or label from UNSPEC wrapper X. */ |
| #define UNSPEC_ADDRESS(X) \ |
| XVECEXP (X, 0, 0) |
| |
| /* Extract the symbol type from UNSPEC wrapper X. */ |
| #define UNSPEC_ADDRESS_TYPE(X) \ |
| ((enum mips_symbol_type) (XINT (X, 1) - UNSPEC_ADDRESS_FIRST)) |
| |
| /* The maximum distance between the top of the stack frame and the |
| value $sp has when we save & restore registers. |
| |
| Use a maximum gap of 0x100 in the mips16 case. We can then use |
| unextended instructions to save and restore registers, and to |
| allocate and deallocate the top part of the frame. |
| |
| The value in the !mips16 case must be a SMALL_OPERAND and must |
| preserve the maximum stack alignment. */ |
| #define MIPS_MAX_FIRST_STACK_STEP (TARGET_MIPS16 ? 0x100 : 0x7ff0) |
| |
| /* True if INSN is a mips.md pattern or asm statement. */ |
| #define USEFUL_INSN_P(INSN) \ |
| (INSN_P (INSN) \ |
| && GET_CODE (PATTERN (INSN)) != USE \ |
| && GET_CODE (PATTERN (INSN)) != CLOBBER \ |
| && GET_CODE (PATTERN (INSN)) != ADDR_VEC \ |
| && GET_CODE (PATTERN (INSN)) != ADDR_DIFF_VEC) |
| |
| /* If INSN is a delayed branch sequence, return the first instruction |
| in the sequence, otherwise return INSN itself. */ |
| #define SEQ_BEGIN(INSN) \ |
| (INSN_P (INSN) && GET_CODE (PATTERN (INSN)) == SEQUENCE \ |
| ? XVECEXP (PATTERN (INSN), 0, 0) \ |
| : (INSN)) |
| |
| /* Likewise for the last instruction in a delayed branch sequence. */ |
| #define SEQ_END(INSN) \ |
| (INSN_P (INSN) && GET_CODE (PATTERN (INSN)) == SEQUENCE \ |
| ? XVECEXP (PATTERN (INSN), 0, XVECLEN (PATTERN (INSN), 0) - 1) \ |
| : (INSN)) |
| |
| /* Execute the following loop body with SUBINSN set to each instruction |
| between SEQ_BEGIN (INSN) and SEQ_END (INSN) inclusive. */ |
| #define FOR_EACH_SUBINSN(SUBINSN, INSN) \ |
| for ((SUBINSN) = SEQ_BEGIN (INSN); \ |
| (SUBINSN) != NEXT_INSN (SEQ_END (INSN)); \ |
| (SUBINSN) = NEXT_INSN (SUBINSN)) |
| |
| /* Classifies an address. |
| |
| ADDRESS_REG |
| A natural register + offset address. The register satisfies |
| mips_valid_base_register_p and the offset is a const_arith_operand. |
| |
| ADDRESS_LO_SUM |
| A LO_SUM rtx. The first operand is a valid base register and |
| the second operand is a symbolic address. |
| |
| ADDRESS_CONST_INT |
| A signed 16-bit constant address. |
| |
| ADDRESS_SYMBOLIC: |
| A constant symbolic address (equivalent to CONSTANT_SYMBOLIC). */ |
| enum mips_address_type { |
| ADDRESS_REG, |
| ADDRESS_LO_SUM, |
| ADDRESS_CONST_INT, |
| ADDRESS_SYMBOLIC |
| }; |
| |
| /* Classifies the prototype of a builtin function. */ |
| enum mips_function_type |
| { |
| MIPS_V2SF_FTYPE_V2SF, |
| MIPS_V2SF_FTYPE_V2SF_V2SF, |
| MIPS_V2SF_FTYPE_V2SF_V2SF_INT, |
| MIPS_V2SF_FTYPE_V2SF_V2SF_V2SF_V2SF, |
| MIPS_V2SF_FTYPE_SF_SF, |
| MIPS_INT_FTYPE_V2SF_V2SF, |
| MIPS_INT_FTYPE_V2SF_V2SF_V2SF_V2SF, |
| MIPS_INT_FTYPE_SF_SF, |
| MIPS_INT_FTYPE_DF_DF, |
| MIPS_SF_FTYPE_V2SF, |
| MIPS_SF_FTYPE_SF, |
| MIPS_SF_FTYPE_SF_SF, |
| MIPS_DF_FTYPE_DF, |
| MIPS_DF_FTYPE_DF_DF, |
| |
| /* The last type. */ |
| MIPS_MAX_FTYPE_MAX |
| }; |
| |
| /* Specifies how a builtin function should be converted into rtl. */ |
| enum mips_builtin_type |
| { |
| /* The builtin corresponds directly to an .md pattern. The return |
| value is mapped to operand 0 and the arguments are mapped to |
| operands 1 and above. */ |
| MIPS_BUILTIN_DIRECT, |
| |
| /* The builtin corresponds to a comparison instruction followed by |
| a mips_cond_move_tf_ps pattern. The first two arguments are the |
| values to compare and the second two arguments are the vector |
| operands for the movt.ps or movf.ps instruction (in assembly order). */ |
| MIPS_BUILTIN_MOVF, |
| MIPS_BUILTIN_MOVT, |
| |
| /* The builtin corresponds to a V2SF comparison instruction. Operand 0 |
| of this instruction is the result of the comparison, which has mode |
| CCV2 or CCV4. The function arguments are mapped to operands 1 and |
| above. The function's return value is an SImode boolean that is |
| true under the following conditions: |
| |
| MIPS_BUILTIN_CMP_ANY: one of the registers is true |
| MIPS_BUILTIN_CMP_ALL: all of the registers are true |
| MIPS_BUILTIN_CMP_LOWER: the first register is true |
| MIPS_BUILTIN_CMP_UPPER: the second register is true. */ |
| MIPS_BUILTIN_CMP_ANY, |
| MIPS_BUILTIN_CMP_ALL, |
| MIPS_BUILTIN_CMP_UPPER, |
| MIPS_BUILTIN_CMP_LOWER, |
| |
| /* As above, but the instruction only sets a single $fcc register. */ |
| MIPS_BUILTIN_CMP_SINGLE |
| }; |
| |
| /* Invokes MACRO (COND) for each c.cond.fmt condition. */ |
| #define MIPS_FP_CONDITIONS(MACRO) \ |
| MACRO (f), \ |
| MACRO (un), \ |
| MACRO (eq), \ |
| MACRO (ueq), \ |
| MACRO (olt), \ |
| MACRO (ult), \ |
| MACRO (ole), \ |
| MACRO (ule), \ |
| MACRO (sf), \ |
| MACRO (ngle), \ |
| MACRO (seq), \ |
| MACRO (ngl), \ |
| MACRO (lt), \ |
| MACRO (nge), \ |
| MACRO (le), \ |
| MACRO (ngt) |
| |
| /* Enumerates the codes above as MIPS_FP_COND_<X>. */ |
| #define DECLARE_MIPS_COND(X) MIPS_FP_COND_ ## X |
| enum mips_fp_condition { |
| MIPS_FP_CONDITIONS (DECLARE_MIPS_COND) |
| }; |
| |
| /* Index X provides the string representation of MIPS_FP_COND_<X>. */ |
| #define STRINGIFY(X) #X |
| static const char *const mips_fp_conditions[] = { |
| MIPS_FP_CONDITIONS (STRINGIFY) |
| }; |
| |
| /* A function to save or store a register. The first argument is the |
| register and the second is the stack slot. */ |
| typedef void (*mips_save_restore_fn) (rtx, rtx); |
| |
| struct mips16_constant; |
| struct mips_arg_info; |
| struct mips_address_info; |
| struct mips_integer_op; |
| struct mips_sim; |
| |
| static enum mips_symbol_type mips_classify_symbol (rtx); |
| static void mips_split_const (rtx, rtx *, HOST_WIDE_INT *); |
| static bool mips_offset_within_object_p (rtx, HOST_WIDE_INT); |
| static bool mips_valid_base_register_p (rtx, enum machine_mode, int); |
| static bool mips_symbolic_address_p (enum mips_symbol_type, enum machine_mode); |
| static bool mips_classify_address (struct mips_address_info *, rtx, |
| enum machine_mode, int); |
| static int mips_symbol_insns (enum mips_symbol_type); |
| static bool mips16_unextended_reference_p (enum machine_mode mode, rtx, rtx); |
| static rtx mips_force_temporary (rtx, rtx); |
| static rtx mips_split_symbol (rtx, rtx); |
| static rtx mips_unspec_offset_high (rtx, rtx, rtx, enum mips_symbol_type); |
| static rtx mips_add_offset (rtx, rtx, HOST_WIDE_INT); |
| static unsigned int mips_build_shift (struct mips_integer_op *, HOST_WIDE_INT); |
| static unsigned int mips_build_lower (struct mips_integer_op *, |
| unsigned HOST_WIDE_INT); |
| static unsigned int mips_build_integer (struct mips_integer_op *, |
| unsigned HOST_WIDE_INT); |
| static void mips_move_integer (rtx, unsigned HOST_WIDE_INT); |
| static void mips_legitimize_const_move (enum machine_mode, rtx, rtx); |
| static int m16_check_op (rtx, int, int, int); |
| static bool mips_rtx_costs (rtx, int, int, int *); |
| static int mips_address_cost (rtx); |
| static void mips_emit_compare (enum rtx_code *, rtx *, rtx *, bool); |
| static void mips_load_call_address (rtx, rtx, int); |
| static bool mips_function_ok_for_sibcall (tree, tree); |
| static void mips_block_move_straight (rtx, rtx, HOST_WIDE_INT); |
| static void mips_adjust_block_mem (rtx, HOST_WIDE_INT, rtx *, rtx *); |
| static void mips_block_move_loop (rtx, rtx, HOST_WIDE_INT); |
| static void mips_arg_info (const CUMULATIVE_ARGS *, enum machine_mode, |
| tree, int, struct mips_arg_info *); |
| static bool mips_get_unaligned_mem (rtx *, unsigned int, int, rtx *, rtx *); |
| static void mips_set_architecture (const struct mips_cpu_info *); |
| static void mips_set_tune (const struct mips_cpu_info *); |
| static struct machine_function *mips_init_machine_status (void); |
| static void print_operand_reloc (FILE *, rtx, const char **); |
| #if TARGET_IRIX |
| static void irix_output_external_libcall (rtx); |
| #endif |
| static void mips_file_start (void); |
| static void mips_file_end (void); |
| static bool mips_rewrite_small_data_p (rtx); |
| static int mips_small_data_pattern_1 (rtx *, void *); |
| static int mips_rewrite_small_data_1 (rtx *, void *); |
| static bool mips_function_has_gp_insn (void); |
| static unsigned int mips_global_pointer (void); |
| static bool mips_save_reg_p (unsigned int); |
| static void mips_save_restore_reg (enum machine_mode, int, HOST_WIDE_INT, |
| mips_save_restore_fn); |
| static void mips_for_each_saved_reg (HOST_WIDE_INT, mips_save_restore_fn); |
| static void mips_output_cplocal (void); |
| static void mips_emit_loadgp (void); |
| static void mips_output_function_prologue (FILE *, HOST_WIDE_INT); |
| static void mips_set_frame_expr (rtx); |
| static rtx mips_frame_set (rtx, rtx); |
| static void mips_save_reg (rtx, rtx); |
| static void mips_output_function_epilogue (FILE *, HOST_WIDE_INT); |
| static void mips_restore_reg (rtx, rtx); |
| static void mips_output_mi_thunk (FILE *, tree, HOST_WIDE_INT, |
| HOST_WIDE_INT, tree); |
| static int symbolic_expression_p (rtx); |
| static void mips_select_rtx_section (enum machine_mode, rtx, |
| unsigned HOST_WIDE_INT); |
| static void mips_function_rodata_section (tree); |
| static bool mips_in_small_data_p (tree); |
| static int mips_fpr_return_fields (tree, tree *); |
| static bool mips_return_in_msb (tree); |
| static rtx mips_return_fpr_pair (enum machine_mode mode, |
| enum machine_mode mode1, HOST_WIDE_INT, |
| enum machine_mode mode2, HOST_WIDE_INT); |
| static rtx mips16_gp_pseudo_reg (void); |
| static void mips16_fp_args (FILE *, int, int); |
| static void build_mips16_function_stub (FILE *); |
| static rtx dump_constants_1 (enum machine_mode, rtx, rtx); |
| static void dump_constants (struct mips16_constant *, rtx); |
| static int mips16_insn_length (rtx); |
| static int mips16_rewrite_pool_refs (rtx *, void *); |
| static void mips16_lay_out_constants (void); |
| static void mips_sim_reset (struct mips_sim *); |
| static void mips_sim_init (struct mips_sim *, state_t); |
| static void mips_sim_next_cycle (struct mips_sim *); |
| static void mips_sim_wait_reg (struct mips_sim *, rtx, rtx); |
| static int mips_sim_wait_regs_2 (rtx *, void *); |
| static void mips_sim_wait_regs_1 (rtx *, void *); |
| static void mips_sim_wait_regs (struct mips_sim *, rtx); |
| static void mips_sim_wait_units (struct mips_sim *, rtx); |
| static void mips_sim_wait_insn (struct mips_sim *, rtx); |
| static void mips_sim_record_set (rtx, rtx, void *); |
| static void mips_sim_issue_insn (struct mips_sim *, rtx); |
| static void mips_sim_issue_nop (struct mips_sim *); |
| static void mips_sim_finish_insn (struct mips_sim *, rtx); |
| static void vr4130_avoid_branch_rt_conflict (rtx); |
| static void vr4130_align_insns (void); |
| static void mips_avoid_hazard (rtx, rtx, int *, rtx *, rtx); |
| static void mips_avoid_hazards (void); |
| static void mips_reorg (void); |
| static bool mips_strict_matching_cpu_name_p (const char *, const char *); |
| static bool mips_matching_cpu_name_p (const char *, const char *); |
| static const struct mips_cpu_info *mips_parse_cpu (const char *, const char *); |
| static const struct mips_cpu_info *mips_cpu_info_from_isa (int); |
| static bool mips_return_in_memory (tree, tree); |
| static bool mips_strict_argument_naming (CUMULATIVE_ARGS *); |
| static void mips_macc_chains_record (rtx); |
| static void mips_macc_chains_reorder (rtx *, int); |
| static void vr4130_true_reg_dependence_p_1 (rtx, rtx, void *); |
| static bool vr4130_true_reg_dependence_p (rtx); |
| static bool vr4130_swap_insns_p (rtx, rtx); |
| static void vr4130_reorder (rtx *, int); |
| static void mips_promote_ready (rtx *, int, int); |
| static int mips_sched_reorder (FILE *, int, rtx *, int *, int); |
| static int mips_variable_issue (FILE *, int, rtx, int); |
| static int mips_adjust_cost (rtx, rtx, rtx, int); |
| static int mips_issue_rate (void); |
| static int mips_multipass_dfa_lookahead (void); |
| static void mips_init_libfuncs (void); |
| static void mips_setup_incoming_varargs (CUMULATIVE_ARGS *, enum machine_mode, |
| tree, int *, int); |
| static tree mips_build_builtin_va_list (void); |
| static tree mips_gimplify_va_arg_expr (tree, tree, tree *, tree *); |
| static bool mips_pass_by_reference (CUMULATIVE_ARGS *, enum machine_mode mode, |
| tree, bool); |
| static bool mips_callee_copies (CUMULATIVE_ARGS *, enum machine_mode mode, |
| tree, bool); |
| static int mips_arg_partial_bytes (CUMULATIVE_ARGS *, enum machine_mode mode, |
| tree, bool); |
| static bool mips_valid_pointer_mode (enum machine_mode); |
| static bool mips_scalar_mode_supported_p (enum machine_mode); |
| static bool mips_vector_mode_supported_p (enum machine_mode); |
| static rtx mips_prepare_builtin_arg (enum insn_code, unsigned int, tree *); |
| static rtx mips_prepare_builtin_target (enum insn_code, unsigned int, rtx); |
| static rtx mips_expand_builtin (tree, rtx, rtx, enum machine_mode, int); |
| static void mips_init_builtins (void); |
| static rtx mips_expand_builtin_direct (enum insn_code, rtx, tree); |
| static rtx mips_expand_builtin_movtf (enum mips_builtin_type, |
| enum insn_code, enum mips_fp_condition, |
| rtx, tree); |
| static rtx mips_expand_builtin_compare (enum mips_builtin_type, |
| enum insn_code, enum mips_fp_condition, |
| rtx, tree); |
| |
| /* Structure to be filled in by compute_frame_size with register |
| save masks, and offsets for the current function. */ |
| |
| struct mips_frame_info GTY(()) |
| { |
| HOST_WIDE_INT total_size; /* # bytes that the entire frame takes up */ |
| HOST_WIDE_INT var_size; /* # bytes that variables take up */ |
| HOST_WIDE_INT args_size; /* # bytes that outgoing arguments take up */ |
| HOST_WIDE_INT cprestore_size; /* # bytes that the .cprestore slot takes up */ |
| HOST_WIDE_INT gp_reg_size; /* # bytes needed to store gp regs */ |
| HOST_WIDE_INT fp_reg_size; /* # bytes needed to store fp regs */ |
| unsigned int mask; /* mask of saved gp registers */ |
| unsigned int fmask; /* mask of saved fp registers */ |
| HOST_WIDE_INT gp_save_offset; /* offset from vfp to store gp registers */ |
| HOST_WIDE_INT fp_save_offset; /* offset from vfp to store fp registers */ |
| HOST_WIDE_INT gp_sp_offset; /* offset from new sp to store gp registers */ |
| HOST_WIDE_INT fp_sp_offset; /* offset from new sp to store fp registers */ |
| bool initialized; /* true if frame size already calculated */ |
| int num_gp; /* number of gp registers saved */ |
| int num_fp; /* number of fp registers saved */ |
| }; |
| |
| struct machine_function GTY(()) { |
| /* Pseudo-reg holding the value of $28 in a mips16 function which |
| refers to GP relative global variables. */ |
| rtx mips16_gp_pseudo_rtx; |
| |
| /* Current frame information, calculated by compute_frame_size. */ |
| struct mips_frame_info frame; |
| |
| /* The register to use as the global pointer within this function. */ |
| unsigned int global_pointer; |
| |
| /* True if mips_adjust_insn_length should ignore an instruction's |
| hazard attribute. */ |
| bool ignore_hazard_length_p; |
| |
| /* True if the whole function is suitable for .set noreorder and |
| .set nomacro. */ |
| bool all_noreorder_p; |
| |
| /* True if the function is known to have an instruction that needs $gp. */ |
| bool has_gp_insn_p; |
| }; |
| |
| /* Information about a single argument. */ |
| struct mips_arg_info |
| { |
| /* True if the argument is passed in a floating-point register, or |
| would have been if we hadn't run out of registers. */ |
| bool fpr_p; |
| |
| /* The number of words passed in registers, rounded up. */ |
| unsigned int reg_words; |
| |
| /* For EABI, the offset of the first register from GP_ARG_FIRST or |
| FP_ARG_FIRST. For other ABIs, the offset of the first register from |
| the start of the ABI's argument structure (see the CUMULATIVE_ARGS |
| comment for details). |
| |
| The value is MAX_ARGS_IN_REGISTERS if the argument is passed entirely |
| on the stack. */ |
| unsigned int reg_offset; |
| |
| /* The number of words that must be passed on the stack, rounded up. */ |
| unsigned int stack_words; |
| |
| /* The offset from the start of the stack overflow area of the argument's |
| first stack word. Only meaningful when STACK_WORDS is nonzero. */ |
| unsigned int stack_offset; |
| }; |
| |
| |
| /* Information about an address described by mips_address_type. |
| |
| ADDRESS_CONST_INT |
| No fields are used. |
| |
| ADDRESS_REG |
| REG is the base register and OFFSET is the constant offset. |
| |
| ADDRESS_LO_SUM |
| REG is the register that contains the high part of the address, |
| OFFSET is the symbolic address being referenced and SYMBOL_TYPE |
| is the type of OFFSET's symbol. |
| |
| ADDRESS_SYMBOLIC |
| SYMBOL_TYPE is the type of symbol being referenced. */ |
| |
| struct mips_address_info |
| { |
| enum mips_address_type type; |
| rtx reg; |
| rtx offset; |
| enum mips_symbol_type symbol_type; |
| }; |
| |
| |
| /* One stage in a constant building sequence. These sequences have |
| the form: |
| |
| A = VALUE[0] |
| A = A CODE[1] VALUE[1] |
| A = A CODE[2] VALUE[2] |
| ... |
| |
| where A is an accumulator, each CODE[i] is a binary rtl operation |
| and each VALUE[i] is a constant integer. */ |
| struct mips_integer_op { |
| enum rtx_code code; |
| unsigned HOST_WIDE_INT value; |
| }; |
| |
| |
| /* The largest number of operations needed to load an integer constant. |
| The worst accepted case for 64-bit constants is LUI,ORI,SLL,ORI,SLL,ORI. |
| When the lowest bit is clear, we can try, but reject a sequence with |
| an extra SLL at the end. */ |
| #define MIPS_MAX_INTEGER_OPS 7 |
| |
| |
| /* Global variables for machine-dependent things. */ |
| |
| /* Threshold for data being put into the small data/bss area, instead |
| of the normal data area. */ |
| int mips_section_threshold = -1; |
| |
| /* Count the number of .file directives, so that .loc is up to date. */ |
| int num_source_filenames = 0; |
| |
| /* Count the number of sdb related labels are generated (to find block |
| start and end boundaries). */ |
| int sdb_label_count = 0; |
| |
| /* Next label # for each statement for Silicon Graphics IRIS systems. */ |
| int sym_lineno = 0; |
| |
| /* Linked list of all externals that are to be emitted when optimizing |
| for the global pointer if they haven't been declared by the end of |
| the program with an appropriate .comm or initialization. */ |
| |
| struct extern_list GTY (()) |
| { |
| struct extern_list *next; /* next external */ |
| const char *name; /* name of the external */ |
| int size; /* size in bytes */ |
| }; |
| |
| static GTY (()) struct extern_list *extern_head = 0; |
| |
| /* Name of the file containing the current function. */ |
| const char *current_function_file = ""; |
| |
| /* Number of nested .set noreorder, noat, nomacro, and volatile requests. */ |
| int set_noreorder; |
| int set_noat; |
| int set_nomacro; |
| int set_volatile; |
| |
| /* The next branch instruction is a branch likely, not branch normal. */ |
| int mips_branch_likely; |
| |
| /* The operands passed to the last cmpMM expander. */ |
| rtx cmp_operands[2]; |
| |
| /* The target cpu for code generation. */ |
| enum processor_type mips_arch; |
| const struct mips_cpu_info *mips_arch_info; |
| |
| /* The target cpu for optimization and scheduling. */ |
| enum processor_type mips_tune; |
| const struct mips_cpu_info *mips_tune_info; |
| |
| /* Which instruction set architecture to use. */ |
| int mips_isa; |
| |
| /* Which ABI to use. */ |
| int mips_abi; |
| |
| /* Strings to hold which cpu and instruction set architecture to use. */ |
| const char *mips_arch_string; /* for -march=<xxx> */ |
| const char *mips_tune_string; /* for -mtune=<xxx> */ |
| const char *mips_isa_string; /* for -mips{1,2,3,4} */ |
| const char *mips_abi_string; /* for -mabi={32,n32,64,eabi} */ |
| |
| /* Whether we are generating mips16 hard float code. In mips16 mode |
| we always set TARGET_SOFT_FLOAT; this variable is nonzero if |
| -msoft-float was not specified by the user, which means that we |
| should arrange to call mips32 hard floating point code. */ |
| int mips16_hard_float; |
| |
| const char *mips_cache_flush_func = CACHE_FLUSH_FUNC; |
| |
| /* Holds string <X> if -mfix-vr4130<X> was passed on the command line. */ |
| const char *mips_fix_vr4130_string; |
| |
| /* If TRUE, we split addresses into their high and low parts in the RTL. */ |
| int mips_split_addresses; |
| |
| /* Mode used for saving/restoring general purpose registers. */ |
| static enum machine_mode gpr_mode; |
| |
| /* Array giving truth value on whether or not a given hard register |
| can support a given mode. */ |
| char mips_hard_regno_mode_ok[(int)MAX_MACHINE_MODE][FIRST_PSEUDO_REGISTER]; |
| |
| /* List of all MIPS punctuation characters used by print_operand. */ |
| char mips_print_operand_punct[256]; |
| |
| /* Map GCC register number to debugger register number. */ |
| int mips_dbx_regno[FIRST_PSEUDO_REGISTER]; |
| |
| /* A copy of the original flag_delayed_branch: see override_options. */ |
| static int mips_flag_delayed_branch; |
| |
| static GTY (()) int mips_output_filename_first_time = 1; |
| |
| /* mips_split_p[X] is true if symbols of type X can be split by |
| mips_split_symbol(). */ |
| static bool mips_split_p[NUM_SYMBOL_TYPES]; |
| |
| /* mips_lo_relocs[X] is the relocation to use when a symbol of type X |
| appears in a LO_SUM. It can be null if such LO_SUMs aren't valid or |
| if they are matched by a special .md file pattern. */ |
| static const char *mips_lo_relocs[NUM_SYMBOL_TYPES]; |
| |
| /* Likewise for HIGHs. */ |
| static const char *mips_hi_relocs[NUM_SYMBOL_TYPES]; |
| |
| /* Map hard register number to register class */ |
| const enum reg_class mips_regno_to_class[] = |
| { |
| LEA_REGS, LEA_REGS, M16_NA_REGS, M16_NA_REGS, |
| M16_REGS, M16_REGS, M16_REGS, M16_REGS, |
| LEA_REGS, LEA_REGS, LEA_REGS, LEA_REGS, |
| LEA_REGS, LEA_REGS, LEA_REGS, LEA_REGS, |
| M16_NA_REGS, M16_NA_REGS, LEA_REGS, LEA_REGS, |
| LEA_REGS, LEA_REGS, LEA_REGS, LEA_REGS, |
| T_REG, PIC_FN_ADDR_REG, LEA_REGS, LEA_REGS, |
| LEA_REGS, LEA_REGS, LEA_REGS, LEA_REGS, |
| FP_REGS, FP_REGS, FP_REGS, FP_REGS, |
| FP_REGS, FP_REGS, FP_REGS, FP_REGS, |
| FP_REGS, FP_REGS, FP_REGS, FP_REGS, |
| FP_REGS, FP_REGS, FP_REGS, FP_REGS, |
| FP_REGS, FP_REGS, FP_REGS, FP_REGS, |
| FP_REGS, FP_REGS, FP_REGS, FP_REGS, |
| FP_REGS, FP_REGS, FP_REGS, FP_REGS, |
| FP_REGS, FP_REGS, FP_REGS, FP_REGS, |
| HI_REG, LO_REG, NO_REGS, ST_REGS, |
| ST_REGS, ST_REGS, ST_REGS, ST_REGS, |
| ST_REGS, ST_REGS, ST_REGS, NO_REGS, |
| NO_REGS, ALL_REGS, ALL_REGS, NO_REGS, |
| COP0_REGS, COP0_REGS, COP0_REGS, COP0_REGS, |
| COP0_REGS, COP0_REGS, COP0_REGS, COP0_REGS, |
| COP0_REGS, COP0_REGS, COP0_REGS, COP0_REGS, |
| COP0_REGS, COP0_REGS, COP0_REGS, COP0_REGS, |
| COP0_REGS, COP0_REGS, COP0_REGS, COP0_REGS, |
| COP0_REGS, COP0_REGS, COP0_REGS, COP0_REGS, |
| COP0_REGS, COP0_REGS, COP0_REGS, COP0_REGS, |
| COP0_REGS, COP0_REGS, COP0_REGS, COP0_REGS, |
| COP2_REGS, COP2_REGS, COP2_REGS, COP2_REGS, |
| COP2_REGS, COP2_REGS, COP2_REGS, COP2_REGS, |
| COP2_REGS, COP2_REGS, COP2_REGS, COP2_REGS, |
| COP2_REGS, COP2_REGS, COP2_REGS, COP2_REGS, |
| COP2_REGS, COP2_REGS, COP2_REGS, COP2_REGS, |
| COP2_REGS, COP2_REGS, COP2_REGS, COP2_REGS, |
| COP2_REGS, COP2_REGS, COP2_REGS, COP2_REGS, |
| COP2_REGS, COP2_REGS, COP2_REGS, COP2_REGS, |
| COP3_REGS, COP3_REGS, COP3_REGS, COP3_REGS, |
| COP3_REGS, COP3_REGS, COP3_REGS, COP3_REGS, |
| COP3_REGS, COP3_REGS, COP3_REGS, COP3_REGS, |
| COP3_REGS, COP3_REGS, COP3_REGS, COP3_REGS, |
| COP3_REGS, COP3_REGS, COP3_REGS, COP3_REGS, |
| COP3_REGS, COP3_REGS, COP3_REGS, COP3_REGS, |
| COP3_REGS, COP3_REGS, COP3_REGS, COP3_REGS, |
| COP3_REGS, COP3_REGS, COP3_REGS, COP3_REGS |
| }; |
| |
| /* Map register constraint character to register class. */ |
| enum reg_class mips_char_to_class[256]; |
| |
| /* A table describing all the processors gcc knows about. Names are |
| matched in the order listed. The first mention of an ISA level is |
| taken as the canonical name for that ISA. |
| |
| To ease comparison, please keep this table in the same order as |
| gas's mips_cpu_info_table[]. */ |
| const struct mips_cpu_info mips_cpu_info_table[] = { |
| /* Entries for generic ISAs */ |
| { "mips1", PROCESSOR_R3000, 1 }, |
| { "mips2", PROCESSOR_R6000, 2 }, |
| { "mips3", PROCESSOR_R4000, 3 }, |
| { "mips4", PROCESSOR_R8000, 4 }, |
| { "mips32", PROCESSOR_4KC, 32 }, |
| { "mips32r2", PROCESSOR_M4K, 33 }, |
| { "mips64", PROCESSOR_5KC, 64 }, |
| |
| /* MIPS I */ |
| { "r3000", PROCESSOR_R3000, 1 }, |
| { "r2000", PROCESSOR_R3000, 1 }, /* = r3000 */ |
| { "r3900", PROCESSOR_R3900, 1 }, |
| |
| /* MIPS II */ |
| { "r6000", PROCESSOR_R6000, 2 }, |
| |
| /* MIPS III */ |
| { "r4000", PROCESSOR_R4000, 3 }, |
| { "vr4100", PROCESSOR_R4100, 3 }, |
| { "vr4111", PROCESSOR_R4111, 3 }, |
| { "vr4120", PROCESSOR_R4120, 3 }, |
| { "vr4130", PROCESSOR_R4130, 3 }, |
| { "vr4300", PROCESSOR_R4300, 3 }, |
| { "r4400", PROCESSOR_R4000, 3 }, /* = r4000 */ |
| { "r4600", PROCESSOR_R4600, 3 }, |
| { "orion", PROCESSOR_R4600, 3 }, /* = r4600 */ |
| { "r4650", PROCESSOR_R4650, 3 }, |
| |
| /* MIPS IV */ |
| { "r8000", PROCESSOR_R8000, 4 }, |
| { "vr5000", PROCESSOR_R5000, 4 }, |
| { "vr5400", PROCESSOR_R5400, 4 }, |
| { "vr5500", PROCESSOR_R5500, 4 }, |
| { "rm7000", PROCESSOR_R7000, 4 }, |
| { "rm9000", PROCESSOR_R9000, 4 }, |
| |
| /* MIPS32 */ |
| { "4kc", PROCESSOR_4KC, 32 }, |
| { "4kp", PROCESSOR_4KC, 32 }, /* = 4kc */ |
| |
| /* MIPS32 Release 2 */ |
| { "m4k", PROCESSOR_M4K, 33 }, |
| |
| /* MIPS64 */ |
| { "5kc", PROCESSOR_5KC, 64 }, |
| { "20kc", PROCESSOR_20KC, 64 }, |
| { "sb1", PROCESSOR_SB1, 64 }, |
| { "sr71000", PROCESSOR_SR71000, 64 }, |
| |
| /* End marker */ |
| { 0, 0, 0 } |
| }; |
| |
| /* Nonzero if -march should decide the default value of MASK_SOFT_FLOAT. */ |
| #ifndef MIPS_MARCH_CONTROLS_SOFT_FLOAT |
| #define MIPS_MARCH_CONTROLS_SOFT_FLOAT 0 |
| #endif |
| |
| /* Initialize the GCC target structure. */ |
| #undef TARGET_ASM_ALIGNED_HI_OP |
| #define TARGET_ASM_ALIGNED_HI_OP "\t.half\t" |
| #undef TARGET_ASM_ALIGNED_SI_OP |
| #define TARGET_ASM_ALIGNED_SI_OP "\t.word\t" |
| #undef TARGET_ASM_ALIGNED_DI_OP |
| #define TARGET_ASM_ALIGNED_DI_OP "\t.dword\t" |
| |
| #undef TARGET_ASM_FUNCTION_PROLOGUE |
| #define TARGET_ASM_FUNCTION_PROLOGUE mips_output_function_prologue |
| #undef TARGET_ASM_FUNCTION_EPILOGUE |
| #define TARGET_ASM_FUNCTION_EPILOGUE mips_output_function_epilogue |
| #undef TARGET_ASM_SELECT_RTX_SECTION |
| #define TARGET_ASM_SELECT_RTX_SECTION mips_select_rtx_section |
| #undef TARGET_ASM_FUNCTION_RODATA_SECTION |
| #define TARGET_ASM_FUNCTION_RODATA_SECTION mips_function_rodata_section |
| |
| #undef TARGET_SCHED_REORDER |
| #define TARGET_SCHED_REORDER mips_sched_reorder |
| #undef TARGET_SCHED_VARIABLE_ISSUE |
| #define TARGET_SCHED_VARIABLE_ISSUE mips_variable_issue |
| #undef TARGET_SCHED_ADJUST_COST |
| #define TARGET_SCHED_ADJUST_COST mips_adjust_cost |
| #undef TARGET_SCHED_ISSUE_RATE |
| #define TARGET_SCHED_ISSUE_RATE mips_issue_rate |
| #undef TARGET_SCHED_FIRST_CYCLE_MULTIPASS_DFA_LOOKAHEAD |
| #define TARGET_SCHED_FIRST_CYCLE_MULTIPASS_DFA_LOOKAHEAD \ |
| mips_multipass_dfa_lookahead |
| |
| #undef TARGET_FUNCTION_OK_FOR_SIBCALL |
| #define TARGET_FUNCTION_OK_FOR_SIBCALL mips_function_ok_for_sibcall |
| |
| #undef TARGET_VALID_POINTER_MODE |
| #define TARGET_VALID_POINTER_MODE mips_valid_pointer_mode |
| #undef TARGET_RTX_COSTS |
| #define TARGET_RTX_COSTS mips_rtx_costs |
| #undef TARGET_ADDRESS_COST |
| #define TARGET_ADDRESS_COST mips_address_cost |
| |
| #undef TARGET_IN_SMALL_DATA_P |
| #define TARGET_IN_SMALL_DATA_P mips_in_small_data_p |
| |
| #undef TARGET_MACHINE_DEPENDENT_REORG |
| #define TARGET_MACHINE_DEPENDENT_REORG mips_reorg |
| |
| #undef TARGET_ASM_FILE_START |
| #undef TARGET_ASM_FILE_END |
| #define TARGET_ASM_FILE_START mips_file_start |
| #define TARGET_ASM_FILE_END mips_file_end |
| #undef TARGET_ASM_FILE_START_FILE_DIRECTIVE |
| #define TARGET_ASM_FILE_START_FILE_DIRECTIVE true |
| |
| #undef TARGET_INIT_LIBFUNCS |
| #define TARGET_INIT_LIBFUNCS mips_init_libfuncs |
| |
| #undef TARGET_BUILD_BUILTIN_VA_LIST |
| #define TARGET_BUILD_BUILTIN_VA_LIST mips_build_builtin_va_list |
| #undef TARGET_GIMPLIFY_VA_ARG_EXPR |
| #define TARGET_GIMPLIFY_VA_ARG_EXPR mips_gimplify_va_arg_expr |
| |
| #undef TARGET_PROMOTE_FUNCTION_ARGS |
| #define TARGET_PROMOTE_FUNCTION_ARGS hook_bool_tree_true |
| #undef TARGET_PROMOTE_FUNCTION_RETURN |
| #define TARGET_PROMOTE_FUNCTION_RETURN hook_bool_tree_true |
| #undef TARGET_PROMOTE_PROTOTYPES |
| #define TARGET_PROMOTE_PROTOTYPES hook_bool_tree_true |
| |
| #undef TARGET_RETURN_IN_MEMORY |
| #define TARGET_RETURN_IN_MEMORY mips_return_in_memory |
| #undef TARGET_RETURN_IN_MSB |
| #define TARGET_RETURN_IN_MSB mips_return_in_msb |
| |
| #undef TARGET_ASM_OUTPUT_MI_THUNK |
| #define TARGET_ASM_OUTPUT_MI_THUNK mips_output_mi_thunk |
| #undef TARGET_ASM_CAN_OUTPUT_MI_THUNK |
| #define TARGET_ASM_CAN_OUTPUT_MI_THUNK hook_bool_tree_hwi_hwi_tree_true |
| |
| #undef TARGET_SETUP_INCOMING_VARARGS |
| #define TARGET_SETUP_INCOMING_VARARGS mips_setup_incoming_varargs |
| #undef TARGET_STRICT_ARGUMENT_NAMING |
| #define TARGET_STRICT_ARGUMENT_NAMING mips_strict_argument_naming |
| #undef TARGET_MUST_PASS_IN_STACK |
| #define TARGET_MUST_PASS_IN_STACK must_pass_in_stack_var_size |
| #undef TARGET_PASS_BY_REFERENCE |
| #define TARGET_PASS_BY_REFERENCE mips_pass_by_reference |
| #undef TARGET_CALLEE_COPIES |
| #define TARGET_CALLEE_COPIES mips_callee_copies |
| #undef TARGET_ARG_PARTIAL_BYTES |
| #define TARGET_ARG_PARTIAL_BYTES mips_arg_partial_bytes |
| |
| #undef TARGET_VECTOR_MODE_SUPPORTED_P |
| #define TARGET_VECTOR_MODE_SUPPORTED_P mips_vector_mode_supported_p |
| |
| #undef TARGET_SCALAR_MODE_SUPPORTED_P |
| #define TARGET_SCALAR_MODE_SUPPORTED_P mips_scalar_mode_supported_p |
| |
| #undef TARGET_INIT_BUILTINS |
| #define TARGET_INIT_BUILTINS mips_init_builtins |
| #undef TARGET_EXPAND_BUILTIN |
| #define TARGET_EXPAND_BUILTIN mips_expand_builtin |
| |
| struct gcc_target targetm = TARGET_INITIALIZER; |
| |
| /* Classify symbol X, which must be a SYMBOL_REF or a LABEL_REF. */ |
| |
| static enum mips_symbol_type |
| mips_classify_symbol (rtx x) |
| { |
| if (GET_CODE (x) == LABEL_REF) |
| { |
| if (TARGET_MIPS16) |
| return SYMBOL_CONSTANT_POOL; |
| if (TARGET_ABICALLS) |
| return SYMBOL_GOT_LOCAL; |
| return SYMBOL_GENERAL; |
| } |
| |
| gcc_assert (GET_CODE (x) == SYMBOL_REF); |
| |
| if (CONSTANT_POOL_ADDRESS_P (x)) |
| { |
| if (TARGET_MIPS16) |
| return SYMBOL_CONSTANT_POOL; |
| |
| if (TARGET_ABICALLS) |
| return SYMBOL_GOT_LOCAL; |
| |
| if (GET_MODE_SIZE (get_pool_mode (x)) <= mips_section_threshold) |
| return SYMBOL_SMALL_DATA; |
| |
| return SYMBOL_GENERAL; |
| } |
| |
| if (SYMBOL_REF_SMALL_P (x)) |
| return SYMBOL_SMALL_DATA; |
| |
| if (TARGET_ABICALLS) |
| { |
| if (SYMBOL_REF_DECL (x) == 0) |
| return SYMBOL_REF_LOCAL_P (x) ? SYMBOL_GOT_LOCAL : SYMBOL_GOT_GLOBAL; |
| |
| /* There are three cases to consider: |
| |
| - o32 PIC (either with or without explicit relocs) |
| - n32/n64 PIC without explicit relocs |
| - n32/n64 PIC with explicit relocs |
| |
| In the first case, both local and global accesses will use an |
| R_MIPS_GOT16 relocation. We must correctly predict which of |
| the two semantics (local or global) the assembler and linker |
| will apply. The choice doesn't depend on the symbol's |
| visibility, so we deliberately ignore decl_visibility and |
| binds_local_p here. |
| |
| In the second case, the assembler will not use R_MIPS_GOT16 |
| relocations, but it chooses between local and global accesses |
| in the same way as for o32 PIC. |
| |
| In the third case we have more freedom since both forms of |
| access will work for any kind of symbol. However, there seems |
| little point in doing things differently. */ |
| if (DECL_P (SYMBOL_REF_DECL (x)) && TREE_PUBLIC (SYMBOL_REF_DECL (x))) |
| return SYMBOL_GOT_GLOBAL; |
| |
| return SYMBOL_GOT_LOCAL; |
| } |
| |
| return SYMBOL_GENERAL; |
| } |
| |
| |
| /* Split X into a base and a constant offset, storing them in *BASE |
| and *OFFSET respectively. */ |
| |
| static void |
| mips_split_const (rtx x, rtx *base, HOST_WIDE_INT *offset) |
| { |
| *offset = 0; |
| |
| if (GET_CODE (x) == CONST) |
| x = XEXP (x, 0); |
| |
| if (GET_CODE (x) == PLUS && GET_CODE (XEXP (x, 1)) == CONST_INT) |
| { |
| *offset += INTVAL (XEXP (x, 1)); |
| x = XEXP (x, 0); |
| } |
| *base = x; |
| } |
| |
| |
| /* Return true if SYMBOL is a SYMBOL_REF and OFFSET + SYMBOL points |
| to the same object as SYMBOL. */ |
| |
| static bool |
| mips_offset_within_object_p (rtx symbol, HOST_WIDE_INT offset) |
| { |
| if (GET_CODE (symbol) != SYMBOL_REF) |
| return false; |
| |
| if (CONSTANT_POOL_ADDRESS_P (symbol) |
| && offset >= 0 |
| && offset < (int) GET_MODE_SIZE (get_pool_mode (symbol))) |
| return true; |
| |
| if (SYMBOL_REF_DECL (symbol) != 0 |
| && offset >= 0 |
| && offset < int_size_in_bytes (TREE_TYPE (SYMBOL_REF_DECL (symbol)))) |
| return true; |
| |
| return false; |
| } |
| |
| |
| /* Return true if X is a symbolic constant that can be calculated in |
| the same way as a bare symbol. If it is, store the type of the |
| symbol in *SYMBOL_TYPE. */ |
| |
| bool |
| mips_symbolic_constant_p (rtx x, enum mips_symbol_type *symbol_type) |
| { |
| HOST_WIDE_INT offset; |
| |
| mips_split_const (x, &x, &offset); |
| if (UNSPEC_ADDRESS_P (x)) |
| *symbol_type = UNSPEC_ADDRESS_TYPE (x); |
| else if (GET_CODE (x) == SYMBOL_REF || GET_CODE (x) == LABEL_REF) |
| *symbol_type = mips_classify_symbol (x); |
| else |
| return false; |
| |
| if (offset == 0) |
| return true; |
| |
| /* Check whether a nonzero offset is valid for the underlying |
| relocations. */ |
| switch (*symbol_type) |
| { |
| case SYMBOL_GENERAL: |
| case SYMBOL_64_HIGH: |
| case SYMBOL_64_MID: |
| case SYMBOL_64_LOW: |
| /* If the target has 64-bit pointers and the object file only |
| supports 32-bit symbols, the values of those symbols will be |
| sign-extended. In this case we can't allow an arbitrary offset |
| in case the 32-bit value X + OFFSET has a different sign from X. */ |
| if (Pmode == DImode && !ABI_HAS_64BIT_SYMBOLS) |
| return mips_offset_within_object_p (x, offset); |
| |
| /* In other cases the relocations can handle any offset. */ |
| return true; |
| |
| case SYMBOL_CONSTANT_POOL: |
| /* Allow constant pool references to be converted to LABEL+CONSTANT. |
| In this case, we no longer have access to the underlying constant, |
| but the original symbol-based access was known to be valid. */ |
| if (GET_CODE (x) == LABEL_REF) |
| return true; |
| |
| /* Fall through. */ |
| |
| case SYMBOL_SMALL_DATA: |
| /* Make sure that the offset refers to something within the |
| underlying object. This should guarantee that the final |
| PC- or GP-relative offset is within the 16-bit limit. */ |
| return mips_offset_within_object_p (x, offset); |
| |
| case SYMBOL_GOT_LOCAL: |
| case SYMBOL_GOTOFF_PAGE: |
| /* The linker should provide enough local GOT entries for a |
| 16-bit offset. Larger offsets may lead to GOT overflow. */ |
| return SMALL_OPERAND (offset); |
| |
| case SYMBOL_GOT_GLOBAL: |
| case SYMBOL_GOTOFF_GLOBAL: |
| case SYMBOL_GOTOFF_CALL: |
| case SYMBOL_GOTOFF_LOADGP: |
| return false; |
| } |
| gcc_unreachable (); |
| } |
| |
| |
| /* Return true if X is a symbolic constant whose value is not split |
| into separate relocations. */ |
| |
| bool |
| mips_atomic_symbolic_constant_p (rtx x) |
| { |
| enum mips_symbol_type type; |
| return mips_symbolic_constant_p (x, &type) && !mips_split_p[type]; |
| } |
| |
| |
| /* This function is used to implement REG_MODE_OK_FOR_BASE_P. */ |
| |
| int |
| mips_regno_mode_ok_for_base_p (int regno, enum machine_mode mode, int strict) |
| { |
| if (regno >= FIRST_PSEUDO_REGISTER) |
| { |
| if (!strict) |
| return true; |
| regno = reg_renumber[regno]; |
| } |
| |
| /* These fake registers will be eliminated to either the stack or |
| hard frame pointer, both of which are usually valid base registers. |
| Reload deals with the cases where the eliminated form isn't valid. */ |
| if (regno == ARG_POINTER_REGNUM || regno == FRAME_POINTER_REGNUM) |
| return true; |
| |
| /* In mips16 mode, the stack pointer can only address word and doubleword |
| values, nothing smaller. There are two problems here: |
| |
| (a) Instantiating virtual registers can introduce new uses of the |
| stack pointer. If these virtual registers are valid addresses, |
| the stack pointer should be too. |
| |
| (b) Most uses of the stack pointer are not made explicit until |
| FRAME_POINTER_REGNUM and ARG_POINTER_REGNUM have been eliminated. |
| We don't know until that stage whether we'll be eliminating to the |
| stack pointer (which needs the restriction) or the hard frame |
| pointer (which doesn't). |
| |
| All in all, it seems more consistent to only enforce this restriction |
| during and after reload. */ |
| if (TARGET_MIPS16 && regno == STACK_POINTER_REGNUM) |
| return !strict || GET_MODE_SIZE (mode) == 4 || GET_MODE_SIZE (mode) == 8; |
| |
| return TARGET_MIPS16 ? M16_REG_P (regno) : GP_REG_P (regno); |
| } |
| |
| |
| /* Return true if X is a valid base register for the given mode. |
| Allow only hard registers if STRICT. */ |
| |
| static bool |
| mips_valid_base_register_p (rtx x, enum machine_mode mode, int strict) |
| { |
| if (!strict && GET_CODE (x) == SUBREG) |
| x = SUBREG_REG (x); |
| |
| return (REG_P (x) |
| && mips_regno_mode_ok_for_base_p (REGNO (x), mode, strict)); |
| } |
| |
| |
| /* Return true if symbols of type SYMBOL_TYPE can directly address a value |
| with mode MODE. This is used for both symbolic and LO_SUM addresses. */ |
| |
| static bool |
| mips_symbolic_address_p (enum mips_symbol_type symbol_type, |
| enum machine_mode mode) |
| { |
| switch (symbol_type) |
| { |
| case SYMBOL_GENERAL: |
| return !TARGET_MIPS16; |
| |
| case SYMBOL_SMALL_DATA: |
| return true; |
| |
| case SYMBOL_CONSTANT_POOL: |
| /* PC-relative addressing is only available for lw and ld. */ |
| return GET_MODE_SIZE (mode) == 4 || GET_MODE_SIZE (mode) == 8; |
| |
| case SYMBOL_GOT_LOCAL: |
| return true; |
| |
| case SYMBOL_GOT_GLOBAL: |
| /* The address will have to be loaded from the GOT first. */ |
| return false; |
| |
| case SYMBOL_GOTOFF_PAGE: |
| case SYMBOL_GOTOFF_GLOBAL: |
| case SYMBOL_GOTOFF_CALL: |
| case SYMBOL_GOTOFF_LOADGP: |
| case SYMBOL_64_HIGH: |
| case SYMBOL_64_MID: |
| case SYMBOL_64_LOW: |
| return true; |
| } |
| gcc_unreachable (); |
| } |
| |
| |
| /* Return true if X is a valid address for machine mode MODE. If it is, |
| fill in INFO appropriately. STRICT is true if we should only accept |
| hard base registers. */ |
| |
| static bool |
| mips_classify_address (struct mips_address_info *info, rtx x, |
| enum machine_mode mode, int strict) |
| { |
| switch (GET_CODE (x)) |
| { |
| case REG: |
| case SUBREG: |
| info->type = ADDRESS_REG; |
| info->reg = x; |
| info->offset = const0_rtx; |
| return mips_valid_base_register_p (info->reg, mode, strict); |
| |
| case PLUS: |
| info->type = ADDRESS_REG; |
| info->reg = XEXP (x, 0); |
| info->offset = XEXP (x, 1); |
| return (mips_valid_base_register_p (info->reg, mode, strict) |
| && const_arith_operand (info->offset, VOIDmode)); |
| |
| case LO_SUM: |
| info->type = ADDRESS_LO_SUM; |
| info->reg = XEXP (x, 0); |
| info->offset = XEXP (x, 1); |
| return (mips_valid_base_register_p (info->reg, mode, strict) |
| && mips_symbolic_constant_p (info->offset, &info->symbol_type) |
| && mips_symbolic_address_p (info->symbol_type, mode) |
| && mips_lo_relocs[info->symbol_type] != 0); |
| |
| case CONST_INT: |
| /* Small-integer addresses don't occur very often, but they |
| are legitimate if $0 is a valid base register. */ |
| info->type = ADDRESS_CONST_INT; |
| return !TARGET_MIPS16 && SMALL_INT (x); |
| |
| case CONST: |
| case LABEL_REF: |
| case SYMBOL_REF: |
| info->type = ADDRESS_SYMBOLIC; |
| return (mips_symbolic_constant_p (x, &info->symbol_type) |
| && mips_symbolic_address_p (info->symbol_type, mode) |
| && !mips_split_p[info->symbol_type]); |
| |
| default: |
| return false; |
| } |
| } |
| |
| /* Return the number of instructions needed to load a symbol of the |
| given type into a register. If valid in an address, the same number |
| of instructions are needed for loads and stores. Treat extended |
| mips16 instructions as two instructions. */ |
| |
| static int |
| mips_symbol_insns (enum mips_symbol_type type) |
| { |
| switch (type) |
| { |
| case SYMBOL_GENERAL: |
| /* In mips16 code, general symbols must be fetched from the |
| constant pool. */ |
| if (TARGET_MIPS16) |
| return 0; |
| |
| /* When using 64-bit symbols, we need 5 preparatory instructions, |
| such as: |
| |
| lui $at,%highest(symbol) |
| daddiu $at,$at,%higher(symbol) |
| dsll $at,$at,16 |
| daddiu $at,$at,%hi(symbol) |
| dsll $at,$at,16 |
| |
| The final address is then $at + %lo(symbol). With 32-bit |
| symbols we just need a preparatory lui. */ |
| return (ABI_HAS_64BIT_SYMBOLS ? 6 : 2); |
| |
| case SYMBOL_SMALL_DATA: |
| return 1; |
| |
| case SYMBOL_CONSTANT_POOL: |
| /* This case is for mips16 only. Assume we'll need an |
| extended instruction. */ |
| return 2; |
| |
| case SYMBOL_GOT_LOCAL: |
| case SYMBOL_GOT_GLOBAL: |
| /* Unless -funit-at-a-time is in effect, we can't be sure whether |
| the local/global classification is accurate. See override_options |
| for details. |
| |
| The worst cases are: |
| |
| (1) For local symbols when generating o32 or o64 code. The assembler |
| will use: |
| |
| lw $at,%got(symbol) |
| nop |
| |
| ...and the final address will be $at + %lo(symbol). |
| |
| (2) For global symbols when -mxgot. The assembler will use: |
| |
| lui $at,%got_hi(symbol) |
| (d)addu $at,$at,$gp |
| |
| ...and the final address will be $at + %got_lo(symbol). */ |
| return 3; |
| |
| case SYMBOL_GOTOFF_PAGE: |
| case SYMBOL_GOTOFF_GLOBAL: |
| case SYMBOL_GOTOFF_CALL: |
| case SYMBOL_GOTOFF_LOADGP: |
| case SYMBOL_64_HIGH: |
| case SYMBOL_64_MID: |
| case SYMBOL_64_LOW: |
| /* Check whether the offset is a 16- or 32-bit value. */ |
| return mips_split_p[type] ? 2 : 1; |
| } |
| gcc_unreachable (); |
| } |
| |
| /* Return true if X is a legitimate $sp-based address for mode MDOE. */ |
| |
| bool |
| mips_stack_address_p (rtx x, enum machine_mode mode) |
| { |
| struct mips_address_info addr; |
| |
| return (mips_classify_address (&addr, x, mode, false) |
| && addr.type == ADDRESS_REG |
| && addr.reg == stack_pointer_rtx); |
| } |
| |
| /* Return true if a value at OFFSET bytes from BASE can be accessed |
| using an unextended mips16 instruction. MODE is the mode of the |
| value. |
| |
| Usually the offset in an unextended instruction is a 5-bit field. |
| The offset is unsigned and shifted left once for HIs, twice |
| for SIs, and so on. An exception is SImode accesses off the |
| stack pointer, which have an 8-bit immediate field. */ |
| |
| static bool |
| mips16_unextended_reference_p (enum machine_mode mode, rtx base, rtx offset) |
| { |
| if (TARGET_MIPS16 |
| && GET_CODE (offset) == CONST_INT |
| && INTVAL (offset) >= 0 |
| && (INTVAL (offset) & (GET_MODE_SIZE (mode) - 1)) == 0) |
| { |
| if (GET_MODE_SIZE (mode) == 4 && base == stack_pointer_rtx) |
| return INTVAL (offset) < 256 * GET_MODE_SIZE (mode); |
| return INTVAL (offset) < 32 * GET_MODE_SIZE (mode); |
| } |
| return false; |
| } |
| |
| |
| /* Return the number of instructions needed to load or store a value |
| of mode MODE at X. Return 0 if X isn't valid for MODE. |
| |
| For mips16 code, count extended instructions as two instructions. */ |
| |
| int |
| mips_address_insns (rtx x, enum machine_mode mode) |
| { |
| struct mips_address_info addr; |
| int factor; |
| |
| if (mode == BLKmode) |
| /* BLKmode is used for single unaligned loads and stores. */ |
| factor = 1; |
| else |
| /* Each word of a multi-word value will be accessed individually. */ |
| factor = (GET_MODE_SIZE (mode) + UNITS_PER_WORD - 1) / UNITS_PER_WORD; |
| |
| if (mips_classify_address (&addr, x, mode, false)) |
| switch (addr.type) |
| { |
| case ADDRESS_REG: |
| if (TARGET_MIPS16 |
| && !mips16_unextended_reference_p (mode, addr.reg, addr.offset)) |
| return factor * 2; |
| return factor; |
| |
| case ADDRESS_LO_SUM: |
| return (TARGET_MIPS16 ? factor * 2 : factor); |
| |
| case ADDRESS_CONST_INT: |
| return factor; |
| |
| case ADDRESS_SYMBOLIC: |
| return factor * mips_symbol_insns (addr.symbol_type); |
| } |
| return 0; |
| } |
| |
| |
| /* Likewise for constant X. */ |
| |
| int |
| mips_const_insns (rtx x) |
| { |
| struct mips_integer_op codes[MIPS_MAX_INTEGER_OPS]; |
| enum mips_symbol_type symbol_type; |
| HOST_WIDE_INT offset; |
| |
| switch (GET_CODE (x)) |
| { |
| case HIGH: |
| if (TARGET_MIPS16 |
| || !mips_symbolic_constant_p (XEXP (x, 0), &symbol_type) |
| || !mips_split_p[symbol_type]) |
| return 0; |
| |
| return 1; |
| |
| case CONST_INT: |
| if (TARGET_MIPS16) |
| /* Unsigned 8-bit constants can be loaded using an unextended |
| LI instruction. Unsigned 16-bit constants can be loaded |
| using an extended LI. Negative constants must be loaded |
| using LI and then negated. */ |
| return (INTVAL (x) >= 0 && INTVAL (x) < 256 ? 1 |
| : SMALL_OPERAND_UNSIGNED (INTVAL (x)) ? 2 |
| : INTVAL (x) > -256 && INTVAL (x) < 0 ? 2 |
| : SMALL_OPERAND_UNSIGNED (-INTVAL (x)) ? 3 |
| : 0); |
| |
| return mips_build_integer (codes, INTVAL (x)); |
| |
| case CONST_DOUBLE: |
| case CONST_VECTOR: |
| return (!TARGET_MIPS16 && x == CONST0_RTX (GET_MODE (x)) ? 1 : 0); |
| |
| case CONST: |
| if (CONST_GP_P (x)) |
| return 1; |
| |
| /* See if we can refer to X directly. */ |
| if (mips_symbolic_constant_p (x, &symbol_type)) |
| return mips_symbol_insns (symbol_type); |
| |
| /* Otherwise try splitting the constant into a base and offset. |
| 16-bit offsets can be added using an extra addiu. Larger offsets |
| must be calculated separately and then added to the base. */ |
| mips_split_const (x, &x, &offset); |
| if (offset != 0) |
| { |
| int n = mips_const_insns (x); |
| if (n != 0) |
| { |
| if (SMALL_OPERAND (offset)) |
| return n + 1; |
| else |
| return n + 1 + mips_build_integer (codes, offset); |
| } |
| } |
| return 0; |
| |
| case SYMBOL_REF: |
| case LABEL_REF: |
| return mips_symbol_insns (mips_classify_symbol (x)); |
| |
| default: |
| return 0; |
| } |
| } |
| |
| |
| /* Return the number of instructions needed for memory reference X. |
| Count extended mips16 instructions as two instructions. */ |
| |
| int |
| mips_fetch_insns (rtx x) |
| { |
| gcc_assert (MEM_P (x)); |
| return mips_address_insns (XEXP (x, 0), GET_MODE (x)); |
| } |
| |
| |
| /* Return the number of instructions needed for an integer division. */ |
| |
| int |
| mips_idiv_insns (void) |
| { |
| int count; |
| |
| count = 1; |
| if (TARGET_CHECK_ZERO_DIV) |
| { |
| if (GENERATE_DIVIDE_TRAPS) |
| count++; |
| else |
| count += 2; |
| } |
| |
| if (TARGET_FIX_R4000 || TARGET_FIX_R4400) |
| count++; |
| return count; |
| } |
| |
| /* This function is used to implement GO_IF_LEGITIMATE_ADDRESS. It |
| returns a nonzero value if X is a legitimate address for a memory |
| operand of the indicated MODE. STRICT is nonzero if this function |
| is called during reload. */ |
| |
| bool |
| mips_legitimate_address_p (enum machine_mode mode, rtx x, int strict) |
| { |
| struct mips_address_info addr; |
| |
| return mips_classify_address (&addr, x, mode, strict); |
| } |
| |
| |
| /* Copy VALUE to a register and return that register. If new psuedos |
| are allowed, copy it into a new register, otherwise use DEST. */ |
| |
| static rtx |
| mips_force_temporary (rtx dest, rtx value) |
| { |
| if (!no_new_pseudos) |
| return force_reg (Pmode, value); |
| else |
| { |
| emit_move_insn (copy_rtx (dest), value); |
| return dest; |
| } |
| } |
| |
| |
| /* Return a LO_SUM expression for ADDR. TEMP is as for mips_force_temporary |
| and is used to load the high part into a register. */ |
| |
| static rtx |
| mips_split_symbol (rtx temp, rtx addr) |
| { |
| rtx high; |
| |
| if (TARGET_MIPS16) |
| high = mips16_gp_pseudo_reg (); |
| else |
| high = mips_force_temporary (temp, gen_rtx_HIGH (Pmode, copy_rtx (addr))); |
| return gen_rtx_LO_SUM (Pmode, high, addr); |
| } |
| |
| |
| /* Return an UNSPEC address with underlying address ADDRESS and symbol |
| type SYMBOL_TYPE. */ |
| |
| rtx |
| mips_unspec_address (rtx address, enum mips_symbol_type symbol_type) |
| { |
| rtx base; |
| HOST_WIDE_INT offset; |
| |
| mips_split_const (address, &base, &offset); |
| base = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, base), |
| UNSPEC_ADDRESS_FIRST + symbol_type); |
| return plus_constant (gen_rtx_CONST (Pmode, base), offset); |
| } |
| |
| |
| /* If mips_unspec_address (ADDR, SYMBOL_TYPE) is a 32-bit value, add the |
| high part to BASE and return the result. Just return BASE otherwise. |
| TEMP is available as a temporary register if needed. |
| |
| The returned expression can be used as the first operand to a LO_SUM. */ |
| |
| static rtx |
| mips_unspec_offset_high (rtx temp, rtx base, rtx addr, |
| enum mips_symbol_type symbol_type) |
| { |
| if (mips_split_p[symbol_type]) |
| { |
| addr = gen_rtx_HIGH (Pmode, mips_unspec_address (addr, symbol_type)); |
| addr = mips_force_temporary (temp, addr); |
| return mips_force_temporary (temp, gen_rtx_PLUS (Pmode, addr, base)); |
| } |
| return base; |
| } |
| |
| |
| /* Return a legitimate address for REG + OFFSET. TEMP is as for |
| mips_force_temporary; it is only needed when OFFSET is not a |
| SMALL_OPERAND. */ |
| |
| static rtx |
| mips_add_offset (rtx temp, rtx reg, HOST_WIDE_INT offset) |
| { |
| if (!SMALL_OPERAND (offset)) |
| { |
| rtx high; |
| if (TARGET_MIPS16) |
| { |
| /* Load the full offset into a register so that we can use |
| an unextended instruction for the address itself. */ |
| high = GEN_INT (offset); |
| offset = 0; |
| } |
| else |
| { |
| /* Leave OFFSET as a 16-bit offset and put the excess in HIGH. */ |
| high = GEN_INT (CONST_HIGH_PART (offset)); |
| offset = CONST_LOW_PART (offset); |
| } |
| high = mips_force_temporary (temp, high); |
| reg = mips_force_temporary (temp, gen_rtx_PLUS (Pmode, high, reg)); |
| } |
| return plus_constant (reg, offset); |
| } |
| |
| |
| /* This function is used to implement LEGITIMIZE_ADDRESS. If *XLOC can |
| be legitimized in a way that the generic machinery might not expect, |
| put the new address in *XLOC and return true. MODE is the mode of |
| the memory being accessed. */ |
| |
| bool |
| mips_legitimize_address (rtx *xloc, enum machine_mode mode) |
| { |
| enum mips_symbol_type symbol_type; |
| |
| /* See if the address can split into a high part and a LO_SUM. */ |
| if (mips_symbolic_constant_p (*xloc, &symbol_type) |
| && mips_symbolic_address_p (symbol_type, mode) |
| && mips_split_p[symbol_type]) |
| { |
| *xloc = mips_split_symbol (0, *xloc); |
| return true; |
| } |
| |
| if (GET_CODE (*xloc) == PLUS && GET_CODE (XEXP (*xloc, 1)) == CONST_INT) |
| { |
| /* Handle REG + CONSTANT using mips_add_offset. */ |
| rtx reg; |
| |
| reg = XEXP (*xloc, 0); |
| if (!mips_valid_base_register_p (reg, mode, 0)) |
| reg = copy_to_mode_reg (Pmode, reg); |
| *xloc = mips_add_offset (0, reg, INTVAL (XEXP (*xloc, 1))); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| |
| /* Subroutine of mips_build_integer (with the same interface). |
| Assume that the final action in the sequence should be a left shift. */ |
| |
| static unsigned int |
| mips_build_shift (struct mips_integer_op *codes, HOST_WIDE_INT value) |
| { |
| unsigned int i, shift; |
| |
| /* Shift VALUE right until its lowest bit is set. Shift arithmetically |
| since signed numbers are easier to load than unsigned ones. */ |
| shift = 0; |
| while ((value & 1) == 0) |
| value /= 2, shift++; |
| |
| i = mips_build_integer (codes, value); |
| codes[i].code = ASHIFT; |
| codes[i].value = shift; |
| return i + 1; |
| } |
| |
| |
| /* As for mips_build_shift, but assume that the final action will be |
| an IOR or PLUS operation. */ |
| |
| static unsigned int |
| mips_build_lower (struct mips_integer_op *codes, unsigned HOST_WIDE_INT value) |
| { |
| unsigned HOST_WIDE_INT high; |
| unsigned int i; |
| |
| high = value & ~(unsigned HOST_WIDE_INT) 0xffff; |
| if (!LUI_OPERAND (high) && (value & 0x18000) == 0x18000) |
| { |
| /* The constant is too complex to load with a simple lui/ori pair |
| so our goal is to clear as many trailing zeros as possible. |
| In this case, we know bit 16 is set and that the low 16 bits |
| form a negative number. If we subtract that number from VALUE, |
| we will clear at least the lowest 17 bits, maybe more. */ |
| i = mips_build_integer (codes, CONST_HIGH_PART (value)); |
| codes[i].code = PLUS; |
| codes[i].value = CONST_LOW_PART (value); |
| } |
| else |
| { |
| i = mips_build_integer (codes, high); |
| codes[i].code = IOR; |
| codes[i].value = value & 0xffff; |
| } |
| return i + 1; |
| } |
| |
| |
| /* Fill CODES with a sequence of rtl operations to load VALUE. |
| Return the number of operations needed. */ |
| |
| static unsigned int |
| mips_build_integer (struct mips_integer_op *codes, |
| unsigned HOST_WIDE_INT value) |
| { |
| if (SMALL_OPERAND (value) |
| || SMALL_OPERAND_UNSIGNED (value) |
| || LUI_OPERAND (value)) |
| { |
| /* The value can be loaded with a single instruction. */ |
| codes[0].code = UNKNOWN; |
| codes[0].value = value; |
| return 1; |
| } |
| else if ((value & 1) != 0 || LUI_OPERAND (CONST_HIGH_PART (value))) |
| { |
| /* Either the constant is a simple LUI/ORI combination or its |
| lowest bit is set. We don't want to shift in this case. */ |
| return mips_build_lower (codes, value); |
| } |
| else if ((value & 0xffff) == 0) |
| { |
| /* The constant will need at least three actions. The lowest |
| 16 bits are clear, so the final action will be a shift. */ |
| return mips_build_shift (codes, value); |
| } |
| else |
| { |
| /* The final action could be a shift, add or inclusive OR. |
| Rather than use a complex condition to select the best |
| approach, try both mips_build_shift and mips_build_lower |
| and pick the one that gives the shortest sequence. |
| Note that this case is only used once per constant. */ |
| struct mips_integer_op alt_codes[MIPS_MAX_INTEGER_OPS]; |
| unsigned int cost, alt_cost; |
| |
| cost = mips_build_shift (codes, value); |
| alt_cost = mips_build_lower (alt_codes, value); |
| if (alt_cost < cost) |
| { |
| memcpy (codes, alt_codes, alt_cost * sizeof (codes[0])); |
| cost = alt_cost; |
| } |
| return cost; |
| } |
| } |
| |
| |
| /* Move VALUE into register DEST. */ |
| |
| static void |
| mips_move_integer (rtx dest, unsigned HOST_WIDE_INT value) |
| { |
| struct mips_integer_op codes[MIPS_MAX_INTEGER_OPS]; |
| enum machine_mode mode; |
| unsigned int i, cost; |
| rtx x; |
| |
| mode = GET_MODE (dest); |
| cost = mips_build_integer (codes, value); |
| |
| /* Apply each binary operation to X. Invariant: X is a legitimate |
| source operand for a SET pattern. */ |
| x = GEN_INT (codes[0].value); |
| for (i = 1; i < cost; i++) |
| { |
| if (no_new_pseudos) |
| emit_move_insn (dest, x), x = dest; |
| else |
| x = force_reg (mode, x); |
| x = gen_rtx_fmt_ee (codes[i].code, mode, x, GEN_INT (codes[i].value)); |
| } |
| |
| emit_insn (gen_rtx_SET (VOIDmode, dest, x)); |
| } |
| |
| |
| /* Subroutine of mips_legitimize_move. Move constant SRC into register |
| DEST given that SRC satisfies immediate_operand but doesn't satisfy |
| move_operand. */ |
| |
| static void |
| mips_legitimize_const_move (enum machine_mode mode, rtx dest, rtx src) |
| { |
| rtx base; |
| HOST_WIDE_INT offset; |
| enum mips_symbol_type symbol_type; |
| |
| /* Split moves of big integers into smaller pieces. In mips16 code, |
| it's better to force the constant into memory instead. */ |
| if (GET_CODE (src) == CONST_INT && !TARGET_MIPS16) |
| { |
| mips_move_integer (dest, INTVAL (src)); |
| return; |
| } |
| |
| /* See if the symbol can be split. For mips16, this is often worse than |
| forcing it in the constant pool since it needs the single-register form |
| of addiu or daddiu. */ |
| if (!TARGET_MIPS16 |
| && mips_symbolic_constant_p (src, &symbol_type) |
| && mips_split_p[symbol_type]) |
| { |
| emit_move_insn (dest, mips_split_symbol (dest, src)); |
| return; |
| } |
| |
| /* If we have (const (plus symbol offset)), load the symbol first |
| and then add in the offset. This is usually better than forcing |
| the constant into memory, at least in non-mips16 code. */ |
| mips_split_const (src, &base, &offset); |
| if (!TARGET_MIPS16 |
| && offset != 0 |
| && (!no_new_pseudos || SMALL_OPERAND (offset))) |
| { |
| base = mips_force_temporary (dest, base); |
| emit_move_insn (dest, mips_add_offset (0, base, offset)); |
| return; |
| } |
| |
| src = force_const_mem (mode, src); |
| |
| /* When using explicit relocs, constant pool references are sometimes |
| not legitimate addresses. */ |
| if (!memory_operand (src, VOIDmode)) |
| src = replace_equiv_address (src, mips_split_symbol (dest, XEXP (src, 0))); |
| emit_move_insn (dest, src); |
| } |
| |
| |
| /* If (set DEST SRC) is not a valid instruction, emit an equivalent |
| sequence that is valid. */ |
| |
| bool |
| mips_legitimize_move (enum machine_mode mode, rtx dest, rtx src) |
| { |
| if (!register_operand (dest, mode) && !reg_or_0_operand (src, mode)) |
| { |
| emit_move_insn (dest, force_reg (mode, src)); |
| return true; |
| } |
| |
| /* Check for individual, fully-reloaded mflo and mfhi instructions. */ |
| if (GET_MODE_SIZE (mode) <= UNITS_PER_WORD |
| && REG_P (src) && MD_REG_P (REGNO (src)) |
| && REG_P (dest) && GP_REG_P (REGNO (dest))) |
| { |
| int other_regno = REGNO (src) == HI_REGNUM ? LO_REGNUM : HI_REGNUM; |
| if (GET_MODE_SIZE (mode) <= 4) |
| emit_insn (gen_mfhilo_si (gen_rtx_REG (SImode, REGNO (dest)), |
| gen_rtx_REG (SImode, REGNO (src)), |
| gen_rtx_REG (SImode, other_regno))); |
| else |
| emit_insn (gen_mfhilo_di (gen_rtx_REG (DImode, REGNO (dest)), |
| gen_rtx_REG (DImode, REGNO (src)), |
| gen_rtx_REG (DImode, other_regno))); |
| return true; |
| } |
| |
| /* We need to deal with constants that would be legitimate |
| immediate_operands but not legitimate move_operands. */ |
| if (CONSTANT_P (src) && !move_operand (src, mode)) |
| { |
| mips_legitimize_const_move (mode, dest, src); |
| set_unique_reg_note (get_last_insn (), REG_EQUAL, copy_rtx (src)); |
| return true; |
| } |
| return false; |
| } |
| |
| /* We need a lot of little routines to check constant values on the |
| mips16. These are used to figure out how long the instruction will |
| be. It would be much better to do this using constraints, but |
| there aren't nearly enough letters available. */ |
| |
| static int |
| m16_check_op (rtx op, int low, int high, int mask) |
| { |
| return (GET_CODE (op) == CONST_INT |
| && INTVAL (op) >= low |
| && INTVAL (op) <= high |
| && (INTVAL (op) & mask) == 0); |
| } |
| |
| int |
| m16_uimm3_b (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return m16_check_op (op, 0x1, 0x8, 0); |
| } |
| |
| int |
| m16_simm4_1 (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return m16_check_op (op, - 0x8, 0x7, 0); |
| } |
| |
| int |
| m16_nsimm4_1 (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return m16_check_op (op, - 0x7, 0x8, 0); |
| } |
| |
| int |
| m16_simm5_1 (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return m16_check_op (op, - 0x10, 0xf, 0); |
| } |
| |
| int |
| m16_nsimm5_1 (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return m16_check_op (op, - 0xf, 0x10, 0); |
| } |
| |
| int |
| m16_uimm5_4 (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return m16_check_op (op, (- 0x10) << 2, 0xf << 2, 3); |
| } |
| |
| int |
| m16_nuimm5_4 (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return m16_check_op (op, (- 0xf) << 2, 0x10 << 2, 3); |
| } |
| |
| int |
| m16_simm8_1 (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return m16_check_op (op, - 0x80, 0x7f, 0); |
| } |
| |
| int |
| m16_nsimm8_1 (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return m16_check_op (op, - 0x7f, 0x80, 0); |
| } |
| |
| int |
| m16_uimm8_1 (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return m16_check_op (op, 0x0, 0xff, 0); |
| } |
| |
| int |
| m16_nuimm8_1 (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return m16_check_op (op, - 0xff, 0x0, 0); |
| } |
| |
| int |
| m16_uimm8_m1_1 (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return m16_check_op (op, - 0x1, 0xfe, 0); |
| } |
| |
| int |
| m16_uimm8_4 (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return m16_check_op (op, 0x0, 0xff << 2, 3); |
| } |
| |
| int |
| m16_nuimm8_4 (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return m16_check_op (op, (- 0xff) << 2, 0x0, 3); |
| } |
| |
| int |
| m16_simm8_8 (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return m16_check_op (op, (- 0x80) << 3, 0x7f << 3, 7); |
| } |
| |
| int |
| m16_nsimm8_8 (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return m16_check_op (op, (- 0x7f) << 3, 0x80 << 3, 7); |
| } |
| |
| static bool |
| mips_rtx_costs (rtx x, int code, int outer_code, int *total) |
| { |
| enum machine_mode mode = GET_MODE (x); |
| |
| switch (code) |
| { |
| case CONST_INT: |
| if (!TARGET_MIPS16) |
| { |
| /* Always return 0, since we don't have different sized |
| instructions, hence different costs according to Richard |
| Kenner */ |
| *total = 0; |
| return true; |
| } |
| |
| /* A number between 1 and 8 inclusive is efficient for a shift. |
| Otherwise, we will need an extended instruction. */ |
| if ((outer_code) == ASHIFT || (outer_code) == ASHIFTRT |
| || (outer_code) == LSHIFTRT) |
| { |
| if (INTVAL (x) >= 1 && INTVAL (x) <= 8) |
| *total = 0; |
| else |
| *total = COSTS_N_INSNS (1); |
| return true; |
| } |
| |
| /* We can use cmpi for an xor with an unsigned 16 bit value. */ |
| if ((outer_code) == XOR |
| && INTVAL (x) >= 0 && INTVAL (x) < 0x10000) |
| { |
| *total = 0; |
| return true; |
| } |
| |
| /* We may be able to use slt or sltu for a comparison with a |
| signed 16 bit value. (The boundary conditions aren't quite |
| right, but this is just a heuristic anyhow.) */ |
| if (((outer_code) == LT || (outer_code) == LE |
| || (outer_code) == GE || (outer_code) == GT |
| || (outer_code) == LTU || (outer_code) == LEU |
| || (outer_code) == GEU || (outer_code) == GTU) |
| && INTVAL (x) >= -0x8000 && INTVAL (x) < 0x8000) |
| { |
| *total = 0; |
| return true; |
| } |
| |
| /* Equality comparisons with 0 are cheap. */ |
| if (((outer_code) == EQ || (outer_code) == NE) |
| && INTVAL (x) == 0) |
| { |
| *total = 0; |
| return true; |
| } |
| |
| /* Constants in the range 0...255 can be loaded with an unextended |
| instruction. They are therefore as cheap as a register move. |
| |
| Given the choice between "li R1,0...255" and "move R1,R2" |
| (where R2 is a known constant), it is usually better to use "li", |
| since we do not want to unnecessarily extend the lifetime of R2. */ |
| if (outer_code == SET |
| && INTVAL (x) >= 0 |
| && INTVAL (x) < 256) |
| { |
| *total = 0; |
| return true; |
| } |
| |
| /* Otherwise fall through to the handling below. */ |
| |
| case CONST: |
| case SYMBOL_REF: |
| case LABEL_REF: |
| case CONST_DOUBLE: |
| if (LEGITIMATE_CONSTANT_P (x)) |
| { |
| *total = COSTS_N_INSNS (1); |
| return true; |
| } |
| else |
| { |
| /* The value will need to be fetched from the constant pool. */ |
| *total = CONSTANT_POOL_COST; |
| return true; |
| } |
| |
| case MEM: |
| { |
| /* If the address is legitimate, return the number of |
| instructions it needs, otherwise use the default handling. */ |
| int n = mips_address_insns (XEXP (x, 0), GET_MODE (x)); |
| if (n > 0) |
| { |
| *total = COSTS_N_INSNS (1 + n); |
| return true; |
| } |
| return false; |
| } |
| |
| case FFS: |
| *total = COSTS_N_INSNS (6); |
| return true; |
| |
| case NOT: |
| *total = COSTS_N_INSNS ((mode == DImode && !TARGET_64BIT) ? 2 : 1); |
| return true; |
| |
| case AND: |
| case IOR: |
| case XOR: |
| if (mode == DImode && !TARGET_64BIT) |
| { |
| *total = COSTS_N_INSNS (2); |
| return true; |
| } |
| return false; |
| |
| case ASHIFT: |
| case ASHIFTRT: |
| case LSHIFTRT: |
| if (mode == DImode && !TARGET_64BIT) |
| { |
| *total = COSTS_N_INSNS ((GET_CODE (XEXP (x, 1)) == CONST_INT) |
| ? 4 : 12); |
| return true; |
| } |
| return false; |
| |
| case ABS: |
| if (mode == SFmode || mode == DFmode) |
| *total = COSTS_N_INSNS (1); |
| else |
| *total = COSTS_N_INSNS (4); |
| return true; |
| |
| case LO_SUM: |
| *total = COSTS_N_INSNS (1); |
| return true; |
| |
| case PLUS: |
| case MINUS: |
| if (mode == SFmode || mode == DFmode) |
| { |
| if (TUNE_MIPS3000 || TUNE_MIPS3900) |
| *total = COSTS_N_INSNS (2); |
| else if (TUNE_MIPS6000) |
| *total = COSTS_N_INSNS (3); |
| else if (TUNE_SB1) |
| *total = COSTS_N_INSNS (4); |
| else |
| *total = COSTS_N_INSNS (6); |
| return true; |
| } |
| if (mode == DImode && !TARGET_64BIT) |
| { |
| *total = COSTS_N_INSNS (4); |
| return true; |
| } |
| return false; |
| |
| case NEG: |
| if (mode == DImode && !TARGET_64BIT) |
| { |
| *total = 4; |
| return true; |
| } |
| return false; |
| |
| case MULT: |
| if (mode == SFmode) |
| { |
| if (TUNE_MIPS3000 |
| || TUNE_MIPS3900 |
| || TUNE_MIPS5000 |
| || TUNE_SB1) |
| *total = COSTS_N_INSNS (4); |
| else if (TUNE_MIPS6000 |
| || TUNE_MIPS5400 |
| || TUNE_MIPS5500) |
| *total = COSTS_N_INSNS (5); |
| else |
| *total = COSTS_N_INSNS (7); |
| return true; |
| } |
| |
| if (mode == DFmode) |
| { |
| if (TUNE_SB1) |
| *total = COSTS_N_INSNS (4); |
| else if (TUNE_MIPS3000 |
| || TUNE_MIPS3900 |
| || TUNE_MIPS5000) |
| *total = COSTS_N_INSNS (5); |
| else if (TUNE_MIPS6000 |
| || TUNE_MIPS5400 |
| || TUNE_MIPS5500) |
| *total = COSTS_N_INSNS (6); |
| else |
| *total = COSTS_N_INSNS (8); |
| return true; |
| } |
| |
| if (TUNE_MIPS3000) |
| *total = COSTS_N_INSNS (12); |
| else if (TUNE_MIPS3900) |
| *total = COSTS_N_INSNS (2); |
| else if (TUNE_MIPS4130) |
| *total = COSTS_N_INSNS (mode == DImode ? 6 : 4); |
| else if (TUNE_MIPS5400 || TUNE_SB1) |
| *total = COSTS_N_INSNS (mode == DImode ? 4 : 3); |
| else if (TUNE_MIPS5500 || TUNE_MIPS7000) |
| *total = COSTS_N_INSNS (mode == DImode ? 9 : 5); |
| else if (TUNE_MIPS9000) |
| *total = COSTS_N_INSNS (mode == DImode ? 8 : 3); |
| else if (TUNE_MIPS6000) |
| *total = COSTS_N_INSNS (17); |
| else if (TUNE_MIPS5000) |
| *total = COSTS_N_INSNS (5); |
| else |
| *total = COSTS_N_INSNS (10); |
| return true; |
| |
| case DIV: |
| case MOD: |
| if (mode == SFmode) |
| { |
| if (TUNE_MIPS3000 |
| || TUNE_MIPS3900) |
| *total = COSTS_N_INSNS (12); |
| else if (TUNE_MIPS6000) |
| *total = COSTS_N_INSNS (15); |
| else if (TUNE_SB1) |
| *total = COSTS_N_INSNS (24); |
| else if (TUNE_MIPS5400 || TUNE_MIPS5500) |
| *total = COSTS_N_INSNS (30); |
| else |
| *total = COSTS_N_INSNS (23); |
| return true; |
| } |
| |
| if (mode == DFmode) |
| { |
| if (TUNE_MIPS3000 |
| || TUNE_MIPS3900) |
| *total = COSTS_N_INSNS (19); |
| else if (TUNE_MIPS5400 || TUNE_MIPS5500) |
| *total = COSTS_N_INSNS (59); |
| else if (TUNE_MIPS6000) |
| *total = COSTS_N_INSNS (16); |
| else if (TUNE_SB1) |
| *total = COSTS_N_INSNS (32); |
| else |
| *total = COSTS_N_INSNS (36); |
| return true; |
| } |
| /* Fall through. */ |
| |
| case UDIV: |
| case UMOD: |
| if (TUNE_MIPS3000 |
| || TUNE_MIPS3900) |
| *total = COSTS_N_INSNS (35); |
| else if (TUNE_MIPS6000) |
| *total = COSTS_N_INSNS (38); |
| else if (TUNE_MIPS5000) |
| *total = COSTS_N_INSNS (36); |
| else if (TUNE_SB1) |
| *total = COSTS_N_INSNS ((mode == SImode) ? 36 : 68); |
| else if (TUNE_MIPS5400 || TUNE_MIPS5500) |
| *total = COSTS_N_INSNS ((mode == SImode) ? 42 : 74); |
| else |
| *total = COSTS_N_INSNS (69); |
| return true; |
| |
| case SIGN_EXTEND: |
| /* A sign extend from SImode to DImode in 64 bit mode is often |
| zero instructions, because the result can often be used |
| directly by another instruction; we'll call it one. */ |
| if (TARGET_64BIT && mode == DImode |
| && GET_MODE (XEXP (x, 0)) == SImode) |
| *total = COSTS_N_INSNS (1); |
| else |
| *total = COSTS_N_INSNS (2); |
| return true; |
| |
| case ZERO_EXTEND: |
| if (TARGET_64BIT && mode == DImode |
| && GET_MODE (XEXP (x, 0)) == SImode) |
| *total = COSTS_N_INSNS (2); |
| else |
| *total = COSTS_N_INSNS (1); |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| /* Provide the costs of an addressing mode that contains ADDR. |
| If ADDR is not a valid address, its cost is irrelevant. */ |
| |
| static int |
| mips_address_cost (rtx addr) |
| { |
| return mips_address_insns (addr, SImode); |
| } |
| |
| /* Return one word of double-word value OP, taking into account the fixed |
| endianness of certain registers. HIGH_P is true to select the high part, |
| false to select the low part. */ |
| |
| rtx |
| mips_subword (rtx op, int high_p) |
| { |
| unsigned int byte; |
| enum machine_mode mode; |
| |
| mode = GET_MODE (op); |
| if (mode == VOIDmode) |
| mode = DImode; |
| |
| if (TARGET_BIG_ENDIAN ? !high_p : high_p) |
| byte = UNITS_PER_WORD; |
| else |
| byte = 0; |
| |
| if (REG_P (op)) |
| { |
| if (FP_REG_P (REGNO (op))) |
| return gen_rtx_REG (word_mode, high_p ? REGNO (op) + 1 : REGNO (op)); |
| if (REGNO (op) == HI_REGNUM) |
| return gen_rtx_REG (word_mode, high_p ? HI_REGNUM : LO_REGNUM); |
| } |
| |
| if (MEM_P (op)) |
| return mips_rewrite_small_data (adjust_address (op, word_mode, byte)); |
| |
| return simplify_gen_subreg (word_mode, op, mode, byte); |
| } |
| |
| |
| /* Return true if a 64-bit move from SRC to DEST should be split into two. */ |
| |
| bool |
| mips_split_64bit_move_p (rtx dest, rtx src) |
| { |
| if (TARGET_64BIT) |
| return false; |
| |
| /* FP->FP moves can be done in a single instruction. */ |
| if (FP_REG_RTX_P (src) && FP_REG_RTX_P (dest)) |
| return false; |
| |
| /* Check for floating-point loads and stores. They can be done using |
| ldc1 and sdc1 on MIPS II and above. */ |
| if (mips_isa > 1) |
| { |
| if (FP_REG_RTX_P (dest) && MEM_P (src)) |
| return false; |
| if (FP_REG_RTX_P (src) && MEM_P (dest)) |
| return false; |
| } |
| return true; |
| } |
| |
| |
| /* Split a 64-bit move from SRC to DEST assuming that |
| mips_split_64bit_move_p holds. |
| |
| Moves into and out of FPRs cause some difficulty here. Such moves |
| will always be DFmode, since paired FPRs are not allowed to store |
| DImode values. The most natural representation would be two separate |
| 32-bit moves, such as: |
| |
| (set (reg:SI $f0) (mem:SI ...)) |
| (set (reg:SI $f1) (mem:SI ...)) |
| |
| However, the second insn is invalid because odd-numbered FPRs are |
| not allowed to store independent values. Use the patterns load_df_low, |
| load_df_high and store_df_high instead. */ |
| |
| void |
| mips_split_64bit_move (rtx dest, rtx src) |
| { |
| if (FP_REG_RTX_P (dest)) |
| { |
| /* Loading an FPR from memory or from GPRs. */ |
| emit_insn (gen_load_df_low (copy_rtx (dest), mips_subword (src, 0))); |
| emit_insn (gen_load_df_high (dest, mips_subword (src, 1), |
| copy_rtx (dest))); |
| } |
| else if (FP_REG_RTX_P (src)) |
| { |
| /* Storing an FPR into memory or GPRs. */ |
| emit_move_insn (mips_subword (dest, 0), mips_subword (src, 0)); |
| emit_insn (gen_store_df_high (mips_subword (dest, 1), src)); |
| } |
| else |
| { |
| /* The operation can be split into two normal moves. Decide in |
| which order to do them. */ |
| rtx low_dest; |
| |
| low_dest = mips_subword (dest, 0); |
| if (REG_P (low_dest) |
| && reg_overlap_mentioned_p (low_dest, src)) |
| { |
| emit_move_insn (mips_subword (dest, 1), mips_subword (src, 1)); |
| emit_move_insn (low_dest, mips_subword (src, 0)); |
| } |
| else |
| { |
| emit_move_insn (low_dest, mips_subword (src, 0)); |
| emit_move_insn (mips_subword (dest, 1), mips_subword (src, 1)); |
| } |
| } |
| } |
| |
| /* Return the appropriate instructions to move SRC into DEST. Assume |
| that SRC is operand 1 and DEST is operand 0. */ |
| |
| const char * |
| mips_output_move (rtx dest, rtx src) |
| { |
| enum rtx_code dest_code, src_code; |
| bool dbl_p; |
| |
| dest_code = GET_CODE (dest); |
| src_code = GET_CODE (src); |
| dbl_p = (GET_MODE_SIZE (GET_MODE (dest)) == 8); |
| |
| if (dbl_p && mips_split_64bit_move_p (dest, src)) |
| return "#"; |
| |
| if ((src_code == REG && GP_REG_P (REGNO (src))) |
| || (!TARGET_MIPS16 && src == CONST0_RTX (GET_MODE (dest)))) |
| { |
| if (dest_code == REG) |
| { |
| if (GP_REG_P (REGNO (dest))) |
| return "move\t%0,%z1"; |
| |
| if (MD_REG_P (REGNO (dest))) |
| return "mt%0\t%z1"; |
| |
| if (FP_REG_P (REGNO (dest))) |
| return (dbl_p ? "dmtc1\t%z1,%0" : "mtc1\t%z1,%0"); |
| |
| if (ALL_COP_REG_P (REGNO (dest))) |
| { |
| static char retval[] = "dmtc_\t%z1,%0"; |
| |
| retval[4] = COPNUM_AS_CHAR_FROM_REGNUM (REGNO (dest)); |
| return (dbl_p ? retval : retval + 1); |
| } |
| } |
| if (dest_code == MEM) |
| return (dbl_p ? "sd\t%z1,%0" : "sw\t%z1,%0"); |
| } |
| if (dest_code == REG && GP_REG_P (REGNO (dest))) |
| { |
| if (src_code == REG) |
| { |
| if (ST_REG_P (REGNO (src)) && ISA_HAS_8CC) |
| return "lui\t%0,0x3f80\n\tmovf\t%0,%.,%1"; |
| |
| if (FP_REG_P (REGNO (src))) |
| return (dbl_p ? "dmfc1\t%0,%1" : "mfc1\t%0,%1"); |
| |
| if (ALL_COP_REG_P (REGNO (src))) |
| { |
| static char retval[] = "dmfc_\t%0,%1"; |
| |
| retval[4] = COPNUM_AS_CHAR_FROM_REGNUM (REGNO (src)); |
| return (dbl_p ? retval : retval + 1); |
| } |
| } |
| |
| if (src_code == MEM) |
| return (dbl_p ? "ld\t%0,%1" : "lw\t%0,%1"); |
| |
| if (src_code == CONST_INT) |
| { |
| /* Don't use the X format, because that will give out of |
| range numbers for 64 bit hosts and 32 bit targets. */ |
| if (!TARGET_MIPS16) |
| return "li\t%0,%1\t\t\t# %X1"; |
| |
| if (INTVAL (src) >= 0 && INTVAL (src) <= 0xffff) |
| return "li\t%0,%1"; |
| |
| if (INTVAL (src) < 0 && INTVAL (src) >= -0xffff) |
| return "#"; |
| } |
| |
| if (src_code == HIGH) |
| return "lui\t%0,%h1"; |
| |
| if (CONST_GP_P (src)) |
| return "move\t%0,%1"; |
| |
| if (symbolic_operand (src, VOIDmode)) |
| return (dbl_p ? "dla\t%0,%1" : "la\t%0,%1"); |
| } |
| if (src_code == REG && FP_REG_P (REGNO (src))) |
| { |
| if (dest_code == REG && FP_REG_P (REGNO (dest))) |
| { |
| if (GET_MODE (dest) == V2SFmode) |
| return "mov.ps\t%0,%1"; |
| else |
| return (dbl_p ? "mov.d\t%0,%1" : "mov.s\t%0,%1"); |
| } |
| |
| if (dest_code == MEM) |
| return (dbl_p ? "sdc1\t%1,%0" : "swc1\t%1,%0"); |
| } |
| if (dest_code == REG && FP_REG_P (REGNO (dest))) |
| { |
| if (src_code == MEM) |
| return (dbl_p ? "ldc1\t%0,%1" : "lwc1\t%0,%1"); |
| } |
| if (dest_code == REG && ALL_COP_REG_P (REGNO (dest)) && src_code == MEM) |
| { |
| static char retval[] = "l_c_\t%0,%1"; |
| |
| retval[1] = (dbl_p ? 'd' : 'w'); |
| retval[3] = COPNUM_AS_CHAR_FROM_REGNUM (REGNO (dest)); |
| return retval; |
| } |
| if (dest_code == MEM && src_code == REG && ALL_COP_REG_P (REGNO (src))) |
| { |
| static char retval[] = "s_c_\t%1,%0"; |
| |
| retval[1] = (dbl_p ? 'd' : 'w'); |
| retval[3] = COPNUM_AS_CHAR_FROM_REGNUM (REGNO (src)); |
| return retval; |
| } |
| gcc_unreachable (); |
| } |
| |
| /* Restore $gp from its save slot. Valid only when using o32 or |
| o64 abicalls. */ |
| |
| void |
| mips_restore_gp (void) |
| { |
| rtx address, slot; |
| |
| gcc_assert (TARGET_ABICALLS && TARGET_OLDABI); |
| |
| address = mips_add_offset (pic_offset_table_rtx, |
| frame_pointer_needed |
| ? hard_frame_pointer_rtx |
| : stack_pointer_rtx, |
| current_function_outgoing_args_size); |
| slot = gen_rtx_MEM (Pmode, address); |
| |
| emit_move_insn (pic_offset_table_rtx, slot); |
| if (!TARGET_EXPLICIT_RELOCS) |
| emit_insn (gen_blockage ()); |
| } |
| |
| /* Emit an instruction of the form (set TARGET (CODE OP0 OP1)). */ |
| |
| static void |
| mips_emit_binary (enum rtx_code code, rtx target, rtx op0, rtx op1) |
| { |
| emit_insn (gen_rtx_SET (VOIDmode, target, |
| gen_rtx_fmt_ee (code, GET_MODE (target), op0, op1))); |
| } |
| |
| /* Return true if CMP1 is a suitable second operand for relational |
| operator CODE. See also the *sCC patterns in mips.md. */ |
| |
| static bool |
| mips_relational_operand_ok_p (enum rtx_code code, rtx cmp1) |
| { |
| switch (code) |
| { |
| case GT: |
| case GTU: |
| return reg_or_0_operand (cmp1, VOIDmode); |
| |
| case GE: |
| case GEU: |
| return !TARGET_MIPS16 && cmp1 == const1_rtx; |
| |
| case LT: |
| case LTU: |
| return arith_operand (cmp1, VOIDmode); |
| |
| case LE: |
| return sle_operand (cmp1, VOIDmode); |
| |
| case LEU: |
| return sleu_operand (cmp1, VOIDmode); |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| /* Compare CMP0 and CMP1 using relational operator CODE and store the |
| result in TARGET. CMP0 and TARGET are register_operands that have |
| the same integer mode. If INVERT_PTR is nonnull, it's OK to set |
| TARGET to the inverse of the result and flip *INVERT_PTR instead. */ |
| |
| static void |
| mips_emit_int_relational (enum rtx_code code, bool *invert_ptr, |
| rtx target, rtx cmp0, rtx cmp1) |
| { |
| /* First see if there is a MIPS instruction that can do this operation |
| with CMP1 in its current form. If not, try doing the same for the |
| inverse operation. If that also fails, force CMP1 into a register |
| and try again. */ |
| if (mips_relational_operand_ok_p (code, cmp1)) |
| mips_emit_binary (code, target, cmp0, cmp1); |
| else |
| { |
| enum rtx_code inv_code = reverse_condition (code); |
| if (!mips_relational_operand_ok_p (inv_code, cmp1)) |
| { |
| cmp1 = force_reg (GET_MODE (cmp0), cmp1); |
| mips_emit_int_relational (code, invert_ptr, target, cmp0, cmp1); |
| } |
| else if (invert_ptr == 0) |
| { |
| rtx inv_target = gen_reg_rtx (GET_MODE (target)); |
| mips_emit_binary (inv_code, inv_target, cmp0, cmp1); |
| mips_emit_binary (XOR, target, inv_target, const1_rtx); |
| } |
| else |
| { |
| *invert_ptr = !*invert_ptr; |
| mips_emit_binary (inv_code, target, cmp0, cmp1); |
| } |
| } |
| } |
| |
| /* Return a register that is zero iff CMP0 and CMP1 are equal. |
| The register will have the same mode as CMP0. */ |
| |
| static rtx |
| mips_zero_if_equal (rtx cmp0, rtx cmp1) |
| { |
| if (cmp1 == const0_rtx) |
| return cmp0; |
| |
| if (uns_arith_operand (cmp1, VOIDmode)) |
| return expand_binop (GET_MODE (cmp0), xor_optab, |
| cmp0, cmp1, 0, 0, OPTAB_DIRECT); |
| |
| return expand_binop (GET_MODE (cmp0), sub_optab, |
| cmp0, cmp1, 0, 0, OPTAB_DIRECT); |
| } |
| |
| /* Convert a comparison into something that can be used in a branch or |
| conditional move. cmp_operands[0] and cmp_operands[1] are the values |
| being compared and *CODE is the code used to compare them. |
| |
| Update *CODE, *OP0 and *OP1 so that they describe the final comparison. |
| If NEED_EQ_NE_P, then only EQ/NE comparisons against zero are possible, |
| otherwise any standard branch condition can be used. The standard branch |
| conditions are: |
| |
| - EQ/NE between two registers. |
| - any comparison between a register and zero. */ |
| |
| static void |
| mips_emit_compare (enum rtx_code *code, rtx *op0, rtx *op1, bool need_eq_ne_p) |
| { |
| if (GET_MODE_CLASS (GET_MODE (cmp_operands[0])) == MODE_INT) |
| { |
| if (!need_eq_ne_p && cmp_operands[1] == const0_rtx) |
| { |
| *op0 = cmp_operands[0]; |
| *op1 = cmp_operands[1]; |
| } |
| else if (*code == EQ || *code == NE) |
| { |
| if (need_eq_ne_p) |
| { |
| *op0 = mips_zero_if_equal (cmp_operands[0], cmp_operands[1]); |
| *op1 = const0_rtx; |
| } |
| else |
| { |
| *op0 = cmp_operands[0]; |
| *op1 = force_reg (GET_MODE (*op0), cmp_operands[1]); |
| } |
| } |
| else |
| { |
| /* The comparison needs a separate scc instruction. Store the |
| result of the scc in *OP0 and compare it against zero. */ |
| bool invert = false; |
| *op0 = gen_reg_rtx (GET_MODE (cmp_operands[0])); |
| *op1 = const0_rtx; |
| mips_emit_int_relational (*code, &invert, *op0, |
| cmp_operands[0], cmp_operands[1]); |
| *code = (invert ? EQ : NE); |
| } |
| } |
| else |
| { |
| enum rtx_code cmp_code; |
| |
| /* Floating-point tests use a separate c.cond.fmt comparison to |
| set a condition code register. The branch or conditional move |
| will then compare that register against zero. |
| |
| Set CMP_CODE to the code of the comparison instruction and |
| *CODE to the code that the branch or move should use. */ |
| switch (*code) |
| { |
| case NE: |
| case LTGT: |
| case ORDERED: |
| cmp_code = reverse_condition_maybe_unordered (*code); |
| *code = EQ; |
| break; |
| |
| default: |
| cmp_code = *code; |
| *code = NE; |
| break; |
| } |
| *op0 = (ISA_HAS_8CC |
| ? gen_reg_rtx (CCmode) |
| : gen_rtx_REG (CCmode, FPSW_REGNUM)); |
| *op1 = const0_rtx; |
| mips_emit_binary (cmp_code, *op0, cmp_operands[0], cmp_operands[1]); |
| } |
| } |
| |
| /* Try comparing cmp_operands[0] and cmp_operands[1] using rtl code CODE. |
| Store the result in TARGET and return true if successful. |
| |
| On 64-bit targets, TARGET may be wider than cmp_operands[0]. */ |
| |
| bool |
| mips_emit_scc (enum rtx_code code, rtx target) |
| { |
| if (GET_MODE_CLASS (GET_MODE (cmp_operands[0])) != MODE_INT) |
| return false; |
| |
| target = gen_lowpart (GET_MODE (cmp_operands[0]), target); |
| if (code == EQ || code == NE) |
| { |
| rtx zie = mips_zero_if_equal (cmp_operands[0], cmp_operands[1]); |
| mips_emit_binary (code, target, zie, const0_rtx); |
| } |
| else |
| mips_emit_int_relational (code, 0, target, |
| cmp_operands[0], cmp_operands[1]); |
| return true; |
| } |
| |
| /* Emit the common code for doing conditional branches. |
| operand[0] is the label to jump to. |
| The comparison operands are saved away by cmp{si,di,sf,df}. */ |
| |
| void |
| gen_conditional_branch (rtx *operands, enum rtx_code code) |
| { |
| rtx op0, op1, target; |
| |
| mips_emit_compare (&code, &op0, &op1, TARGET_MIPS16); |
| target = gen_rtx_IF_THEN_ELSE (VOIDmode, |
| gen_rtx_fmt_ee (code, GET_MODE (op0), |
| op0, op1), |
| gen_rtx_LABEL_REF (VOIDmode, operands[0]), |
| pc_rtx); |
| emit_jump_insn (gen_rtx_SET (VOIDmode, pc_rtx, target)); |
| } |
| |
| /* Emit the common code for conditional moves. OPERANDS is the array |
| of operands passed to the conditional move define_expand. */ |
| |
| void |
| gen_conditional_move (rtx *operands) |
| { |
| enum rtx_code code; |
| rtx op0, op1; |
| |
| code = GET_CODE (operands[1]); |
| mips_emit_compare (&code, &op0, &op1, true); |
| emit_insn (gen_rtx_SET (VOIDmode, operands[0], |
| gen_rtx_IF_THEN_ELSE (GET_MODE (operands[0]), |
| gen_rtx_fmt_ee (code, |
| GET_MODE (op0), |
| op0, op1), |
| operands[2], operands[3]))); |
| } |
| |
| /* Emit a conditional trap. OPERANDS is the array of operands passed to |
| the conditional_trap expander. */ |
| |
| void |
| mips_gen_conditional_trap (rtx *operands) |
| { |
| rtx op0, op1; |
| enum rtx_code cmp_code = GET_CODE (operands[0]); |
| enum machine_mode mode = GET_MODE (cmp_operands[0]); |
| |
| /* MIPS conditional trap machine instructions don't have GT or LE |
| flavors, so we must invert the comparison and convert to LT and |
| GE, respectively. */ |
| switch (cmp_code) |
| { |
| case GT: cmp_code = LT; break; |
| case LE: cmp_code = GE; break; |
| case GTU: cmp_code = LTU; break; |
| case LEU: cmp_code = GEU; break; |
| default: break; |
| } |
| if (cmp_code == GET_CODE (operands[0])) |
| { |
| op0 = cmp_operands[0]; |
| op1 = cmp_operands[1]; |
| } |
| else |
| { |
| op0 = cmp_operands[1]; |
| op1 = cmp_operands[0]; |
| } |
| op0 = force_reg (mode, op0); |
| if (!arith_operand (op1, mode)) |
| op1 = force_reg (mode, op1); |
| |
| emit_insn (gen_rtx_TRAP_IF (VOIDmode, |
| gen_rtx_fmt_ee (cmp_code, mode, op0, op1), |
| operands[1])); |
| } |
| |
| /* Load function address ADDR into register DEST. SIBCALL_P is true |
| if the address is needed for a sibling call. */ |
| |
| static void |
| mips_load_call_address (rtx dest, rtx addr, int sibcall_p) |
| { |
| /* If we're generating PIC, and this call is to a global function, |
| try to allow its address to be resolved lazily. This isn't |
| possible for NewABI sibcalls since the value of $gp on entry |
| to the stub would be our caller's gp, not ours. */ |
| if (TARGET_EXPLICIT_RELOCS |
| && !(sibcall_p && TARGET_NEWABI) |
| && global_got_operand (addr, VOIDmode)) |
| { |
| rtx high, lo_sum_symbol; |
| |
| high = mips_unspec_offset_high (dest, pic_offset_table_rtx, |
| addr, SYMBOL_GOTOFF_CALL); |
| lo_sum_symbol = mips_unspec_address (addr, SYMBOL_GOTOFF_CALL); |
| if (Pmode == SImode) |
| emit_insn (gen_load_callsi (dest, high, lo_sum_symbol)); |
| else |
| emit_insn (gen_load_calldi (dest, high, lo_sum_symbol)); |
| } |
| else |
| emit_move_insn (dest, addr); |
| } |
| |
| |
| /* Expand a call or call_value instruction. RESULT is where the |
| result will go (null for calls), ADDR is the address of the |
| function, ARGS_SIZE is the size of the arguments and AUX is |
| the value passed to us by mips_function_arg. SIBCALL_P is true |
| if we are expanding a sibling call, false if we're expanding |
| a normal call. */ |
| |
| void |
| mips_expand_call (rtx result, rtx addr, rtx args_size, rtx aux, int sibcall_p) |
| { |
| rtx orig_addr, pattern, insn; |
| |
| orig_addr = addr; |
| if (!call_insn_operand (addr, VOIDmode)) |
| { |
| addr = gen_reg_rtx (Pmode); |
| mips_load_call_address (addr, orig_addr, sibcall_p); |
| } |
| |
| if (TARGET_MIPS16 |
| && mips16_hard_float |
| && build_mips16_call_stub (result, addr, args_size, |
| aux == 0 ? 0 : (int) GET_MODE (aux))) |
| return; |
| |
| if (result == 0) |
| pattern = (sibcall_p |
| ? gen_sibcall_internal (addr, args_size) |
| : gen_call_internal (addr, args_size)); |
| else if (GET_CODE (result) == PARALLEL && XVECLEN (result, 0) == 2) |
| { |
| rtx reg1, reg2; |
| |
| reg1 = XEXP (XVECEXP (result, 0, 0), 0); |
| reg2 = XEXP (XVECEXP (result, 0, 1), 0); |
| pattern = |
| (sibcall_p |
| ? gen_sibcall_value_multiple_internal (reg1, addr, args_size, reg2) |
| : gen_call_value_multiple_internal (reg1, addr, args_size, reg2)); |
| } |
| else |
| pattern = (sibcall_p |
| ? gen_sibcall_value_internal (result, addr, args_size) |
| : gen_call_value_internal (result, addr, args_size)); |
| |
| insn = emit_call_insn (pattern); |
| |
| /* Lazy-binding stubs require $gp to be valid on entry. */ |
| if (global_got_operand (orig_addr, VOIDmode)) |
| use_reg (&CALL_INSN_FUNCTION_USAGE (insn), pic_offset_table_rtx); |
| } |
| |
| |
| /* We can handle any sibcall when TARGET_SIBCALLS is true. */ |
| |
| static bool |
| mips_function_ok_for_sibcall (tree decl ATTRIBUTE_UNUSED, |
| tree exp ATTRIBUTE_UNUSED) |
| { |
| return TARGET_SIBCALLS; |
| } |
| |
| /* Emit code to move general operand SRC into condition-code |
| register DEST. SCRATCH is a scratch TFmode float register. |
| The sequence is: |
| |
| FP1 = SRC |
| FP2 = 0.0f |
| DEST = FP2 < FP1 |
| |
| where FP1 and FP2 are single-precision float registers |
| taken from SCRATCH. */ |
| |
| void |
| mips_emit_fcc_reload (rtx dest, rtx src, rtx scratch) |
| { |
| rtx fp1, fp2; |
| |
| /* Change the source to SFmode. */ |
| if (MEM_P (src)) |
| src = adjust_address (src, SFmode, 0); |
| else if (REG_P (src) || GET_CODE (src) == SUBREG) |
| src = gen_rtx_REG (SFmode, true_regnum (src)); |
| |
| fp1 = gen_rtx_REG (SFmode, REGNO (scratch)); |
| fp2 = gen_rtx_REG (SFmode, REGNO (scratch) + FP_INC); |
| |
| emit_move_insn (copy_rtx (fp1), src); |
| emit_move_insn (copy_rtx (fp2), CONST0_RTX (SFmode)); |
| emit_insn (gen_slt_sf (dest, fp2, fp1)); |
| } |
| |
| /* Emit code to change the current function's return address to |
| ADDRESS. SCRATCH is available as a scratch register, if needed. |
| ADDRESS and SCRATCH are both word-mode GPRs. */ |
| |
| void |
| mips_set_return_address (rtx address, rtx scratch) |
| { |
| rtx slot_address; |
| |
| compute_frame_size (get_frame_size ()); |
| gcc_assert ((cfun->machine->frame.mask >> 31) & 1); |
| slot_address = mips_add_offset (scratch, stack_pointer_rtx, |
| cfun->machine->frame.gp_sp_offset); |
| |
| emit_move_insn (gen_rtx_MEM (GET_MODE (address), slot_address), address); |
| } |
| |
| /* Emit straight-line code to move LENGTH bytes from SRC to DEST. |
| Assume that the areas do not overlap. */ |
| |
| static void |
| mips_block_move_straight (rtx dest, rtx src, HOST_WIDE_INT length) |
| { |
| HOST_WIDE_INT offset, delta; |
| unsigned HOST_WIDE_INT bits; |
| int i; |
| enum machine_mode mode; |
| rtx *regs; |
| |
| /* Work out how many bits to move at a time. If both operands have |
| half-word alignment, it is usually better to move in half words. |
| For instance, lh/lh/sh/sh is usually better than lwl/lwr/swl/swr |
| and lw/lw/sw/sw is usually better than ldl/ldr/sdl/sdr. |
| Otherwise move word-sized chunks. */ |
| if (MEM_ALIGN (src) == BITS_PER_WORD / 2 |
| && MEM_ALIGN (dest) == BITS_PER_WORD / 2) |
| bits = BITS_PER_WORD / 2; |
| else |
| bits = BITS_PER_WORD; |
| |
| mode = mode_for_size (bits, MODE_INT, 0); |
| delta = bits / BITS_PER_UNIT; |
| |
| /* Allocate a buffer for the temporary registers. */ |
| regs = alloca (sizeof (rtx) * length / delta); |
| |
| /* Load as many BITS-sized chunks as possible. Use a normal load if |
| the source has enough alignment, otherwise use left/right pairs. */ |
| for (offset = 0, i = 0; offset + delta <= length; offset += delta, i++) |
| { |
| regs[i] = gen_reg_rtx (mode); |
| if (MEM_ALIGN (src) >= bits) |
| emit_move_insn (regs[i], adjust_address (src, mode, offset)); |
| else |
| { |
| rtx part = adjust_address (src, BLKmode, offset); |
| if (!mips_expand_unaligned_load (regs[i], part, bits, 0)) |
| gcc_unreachable (); |
| } |
| } |
| |
| /* Copy the chunks to the destination. */ |
| for (offset = 0, i = 0; offset + delta <= length; offset += delta, i++) |
| if (MEM_ALIGN (dest) >= bits) |
| emit_move_insn (adjust_address (dest, mode, offset), regs[i]); |
| else |
| { |
| rtx part = adjust_address (dest, BLKmode, offset); |
| if (!mips_expand_unaligned_store (part, regs[i], bits, 0)) |
| gcc_unreachable (); |
| } |
| |
| /* Mop up any left-over bytes. */ |
| if (offset < length) |
| { |
| src = adjust_address (src, BLKmode, offset); |
| dest = adjust_address (dest, BLKmode, offset); |
| move_by_pieces (dest, src, length - offset, |
| MIN (MEM_ALIGN (src), MEM_ALIGN (dest)), 0); |
| } |
| } |
| |
| #define MAX_MOVE_REGS 4 |
| #define MAX_MOVE_BYTES (MAX_MOVE_REGS * UNITS_PER_WORD) |
| |
| |
| /* Helper function for doing a loop-based block operation on memory |
| reference MEM. Each iteration of the loop will operate on LENGTH |
| bytes of MEM. |
| |
| Create a new base register for use within the loop and point it to |
| the start of MEM. Create a new memory reference that uses this |
| register. Store them in *LOOP_REG and *LOOP_MEM respectively. */ |
| |
| static void |
| mips_adjust_block_mem (rtx mem, HOST_WIDE_INT length, |
| rtx *loop_reg, rtx *loop_mem) |
| { |
| *loop_reg = copy_addr_to_reg (XEXP (mem, 0)); |
| |
| /* Although the new mem does not refer to a known location, |
| it does keep up to LENGTH bytes of alignment. */ |
| *loop_mem = change_address (mem, BLKmode, *loop_reg); |
| set_mem_align (*loop_mem, MIN (MEM_ALIGN (mem), length * BITS_PER_UNIT)); |
| } |
| |
| |
| /* Move LENGTH bytes from SRC to DEST using a loop that moves MAX_MOVE_BYTES |
| per iteration. LENGTH must be at least MAX_MOVE_BYTES. Assume that the |
| memory regions do not overlap. */ |
| |
| static void |
| mips_block_move_loop (rtx dest, rtx src, HOST_WIDE_INT length) |
| { |
| rtx label, src_reg, dest_reg, final_src; |
| HOST_WIDE_INT leftover; |
| |
| leftover = length % MAX_MOVE_BYTES; |
| length -= leftover; |
| |
| /* Create registers and memory references for use within the loop. */ |
| mips_adjust_block_mem (src, MAX_MOVE_BYTES, &src_reg, &src); |
| mips_adjust_block_mem (dest, MAX_MOVE_BYTES, &dest_reg, &dest); |
| |
| /* Calculate the value that SRC_REG should have after the last iteration |
| of the loop. */ |
| final_src = expand_simple_binop (Pmode, PLUS, src_reg, GEN_INT (length), |
| 0, 0, OPTAB_WIDEN); |
| |
| /* Emit the start of the loop. */ |
| label = gen_label_rtx (); |
| emit_label (label); |
| |
| /* Emit the loop body. */ |
| mips_block_move_straight (dest, src, MAX_MOVE_BYTES); |
| |
| /* Move on to the next block. */ |
| emit_move_insn (src_reg, plus_constant (src_reg, MAX_MOVE_BYTES)); |
| emit_move_insn (dest_reg, plus_constant (dest_reg, MAX_MOVE_BYTES)); |
| |
| /* Emit the loop condition. */ |
| if (Pmode == DImode) |
| emit_insn (gen_cmpdi (src_reg, final_src)); |
| else |
| emit_insn (gen_cmpsi (src_reg, final_src)); |
| emit_jump_insn (gen_bne (label)); |
| |
| /* Mop up any left-over bytes. */ |
| if (leftover) |
| mips_block_move_straight (dest, src, leftover); |
| } |
| |
| /* Expand a movmemsi instruction. */ |
| |
| bool |
| mips_expand_block_move (rtx dest, rtx src, rtx length) |
| { |
| if (GET_CODE (length) == CONST_INT) |
| { |
| if (INTVAL (length) <= 2 * MAX_MOVE_BYTES) |
| { |
| mips_block_move_straight (dest, src, INTVAL (length)); |
| return true; |
| } |
| else if (optimize) |
| { |
| mips_block_move_loop (dest, src, INTVAL (length)); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* Argument support functions. */ |
| |
| /* Initialize CUMULATIVE_ARGS for a function. */ |
| |
| void |
| init_cumulative_args (CUMULATIVE_ARGS *cum, tree fntype, |
| rtx libname ATTRIBUTE_UNUSED) |
| { |
| static CUMULATIVE_ARGS zero_cum; |
| tree param, next_param; |
| |
| *cum = zero_cum; |
| cum->prototype = (fntype && TYPE_ARG_TYPES (fntype)); |
| |
| /* Determine if this function has variable arguments. This is |
| indicated by the last argument being 'void_type_mode' if there |
| are no variable arguments. The standard MIPS calling sequence |
| passes all arguments in the general purpose registers in this case. */ |
| |
| for (param = fntype ? TYPE_ARG_TYPES (fntype) : 0; |
| param != 0; param = next_param) |
| { |
| next_param = TREE_CHAIN (param); |
| if (next_param == 0 && TREE_VALUE (param) != void_type_node) |
| cum->gp_reg_found = 1; |
| } |
| } |
| |
| |
| /* Fill INFO with information about a single argument. CUM is the |
| cumulative state for earlier arguments. MODE is the mode of this |
| argument and TYPE is its type (if known). NAMED is true if this |
| is a named (fixed) argument rather than a variable one. */ |
| |
| static void |
| mips_arg_info (const CUMULATIVE_ARGS *cum, enum machine_mode mode, |
| tree type, int named, struct mips_arg_info *info) |
| { |
| bool doubleword_aligned_p; |
| unsigned int num_bytes, num_words, max_regs; |
| |
| /* Work out the size of the argument. */ |
| num_bytes = type ? int_size_in_bytes (type) : GET_MODE_SIZE (mode); |
| num_words = (num_bytes + UNITS_PER_WORD - 1) / UNITS_PER_WORD; |
| |
| /* Decide whether it should go in a floating-point register, assuming |
| one is free. Later code checks for availability. |
| |
| The checks against UNITS_PER_FPVALUE handle the soft-float and |
| single-float cases. */ |
| switch (mips_abi) |
| { |
| case ABI_EABI: |
| /* The EABI conventions have traditionally been defined in terms |
| of TYPE_MODE, regardless of the actual type. */ |
| info->fpr_p = ((GET_MODE_CLASS (mode) == MODE_FLOAT |
| || GET_MODE_CLASS (mode) == MODE_VECTOR_FLOAT) |
| && GET_MODE_SIZE (mode) <= UNITS_PER_FPVALUE); |
| break; |
| |
| case ABI_32: |
| case ABI_O64: |
| /* Only leading floating-point scalars are passed in |
| floating-point registers. We also handle vector floats the same |
| say, which is OK because they are not covered by the standard ABI. */ |
| info->fpr_p = (!cum->gp_reg_found |
| && cum->arg_number < 2 |
| && (type == 0 || SCALAR_FLOAT_TYPE_P (type) |
| || VECTOR_FLOAT_TYPE_P (type)) |
| && (GET_MODE_CLASS (mode) == MODE_FLOAT |
| || GET_MODE_CLASS (mode) == MODE_VECTOR_FLOAT) |
| && GET_MODE_SIZE (mode) <= UNITS_PER_FPVALUE); |
| break; |
| |
| case ABI_N32: |
| case ABI_64: |
| /* Scalar and complex floating-point types are passed in |
| floating-point registers. */ |
| info->fpr_p = (named |
| && (type == 0 || FLOAT_TYPE_P (type)) |
| && (GET_MODE_CLASS (mode) == MODE_FLOAT |
| || GET_MODE_CLASS (mode) == MODE_COMPLEX_FLOAT |
| || GET_MODE_CLASS (mode) == MODE_VECTOR_FLOAT) |
| && GET_MODE_UNIT_SIZE (mode) <= UNITS_PER_FPVALUE); |
| |
| /* ??? According to the ABI documentation, the real and imaginary |
| parts of complex floats should be passed in individual registers. |
| The real and imaginary parts of stack arguments are supposed |
| to be contiguous and there should be an extra word of padding |
| at the end. |
| |
| This has two problems. First, it makes it impossible to use a |
| single "void *" va_list type, since register and stack arguments |
| are passed differently. (At the time of writing, MIPSpro cannot |
| handle complex float varargs correctly.) Second, it's unclear |
| what should happen when there is only one register free. |
| |
| For now, we assume that named complex floats should go into FPRs |
| if there are two FPRs free, otherwise they should be passed in the |
| same way as a struct containing two floats. */ |
| if (info->fpr_p |
| && GET_MODE_CLASS (mode) == MODE_COMPLEX_FLOAT |
| && GET_MODE_UNIT_SIZE (mode) < UNITS_PER_FPVALUE) |
| { |
| if (cum->num_gprs >= MAX_ARGS_IN_REGISTERS - 1) |
| info->fpr_p = false; |
| else |
| num_words = 2; |
| } |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| /* See whether the argument has doubleword alignment. */ |
| doubleword_aligned_p = FUNCTION_ARG_BOUNDARY (mode, type) > BITS_PER_WORD; |
| |
| /* Set REG_OFFSET to the register count we're interested in. |
| The EABI allocates the floating-point registers separately, |
| but the other ABIs allocate them like integer registers. */ |
| info->reg_offset = (mips_abi == ABI_EABI && info->fpr_p |
| ? cum->num_fprs |
| : cum->num_gprs); |
| |
| /* Advance to an even register if the argument is doubleword-aligned. */ |
| if (doubleword_aligned_p) |
| info->reg_offset += info->reg_offset & 1; |
| |
| /* Work out the offset of a stack argument. */ |
| info->stack_offset = cum->stack_words; |
| if (doubleword_aligned_p) |
| info->stack_offset += info->stack_offset & 1; |
| |
| max_regs = MAX_ARGS_IN_REGISTERS - info->reg_offset; |
| |
| /* Partition the argument between registers and stack. */ |
| info->reg_words = MIN (num_words, max_regs); |
| info->stack_words = num_words - info->reg_words; |
| } |
| |
| |
| /* Implement FUNCTION_ARG_ADVANCE. */ |
| |
| void |
| function_arg_advance (CUMULATIVE_ARGS *cum, enum machine_mode mode, |
| tree type, int named) |
| { |
| struct mips_arg_info info; |
| |
| mips_arg_info (cum, mode, type, named, &info); |
| |
| if (!info.fpr_p) |
| cum->gp_reg_found = true; |
| |
| /* See the comment above the cumulative args structure in mips.h |
| for an explanation of what this code does. It assumes the O32 |
| ABI, which passes at most 2 arguments in float registers. */ |
| if (cum->arg_number < 2 && info.fpr_p) |
| cum->fp_code += (mode == SFmode ? 1 : 2) << ((cum->arg_number - 1) * 2); |
| |
| if (mips_abi != ABI_EABI || !info.fpr_p) |
| cum->num_gprs = info.reg_offset + info.reg_words; |
| else if (info.reg_words > 0) |
| cum->num_fprs += FP_INC; |
| |
| if (info.stack_words > 0) |
| cum->stack_words = info.stack_offset + info.stack_words; |
| |
| cum->arg_number++; |
| } |
| |
| /* Implement FUNCTION_ARG. */ |
| |
| struct rtx_def * |
| function_arg (const CUMULATIVE_ARGS *cum, enum machine_mode mode, |
| tree type, int named) |
| { |
| struct mips_arg_info info; |
| |
| /* We will be called with a mode of VOIDmode after the last argument |
| has been seen. Whatever we return will be passed to the call |
| insn. If we need a mips16 fp_code, return a REG with the code |
| stored as the mode. */ |
| if (mode == VOIDmode) |
| { |
| if (TARGET_MIPS16 && cum->fp_code != 0) |
| return gen_rtx_REG ((enum machine_mode) cum->fp_code, 0); |
| |
| else |
| return 0; |
| } |
| |
| mips_arg_info (cum, mode, type, named, &info); |
| |
| /* Return straight away if the whole argument is passed on the stack. */ |
| if (info.reg_offset == MAX_ARGS_IN_REGISTERS) |
| return 0; |
| |
| if (type != 0 |
| && TREE_CODE (type) == RECORD_TYPE |
| && TARGET_NEWABI |
| && TYPE_SIZE_UNIT (type) |
| && host_integerp (TYPE_SIZE_UNIT (type), 1) |
| && named) |
| { |
| /* The Irix 6 n32/n64 ABIs say that if any 64 bit chunk of the |
| structure contains a double in its entirety, then that 64 bit |
| chunk is passed in a floating point register. */ |
| tree field; |
| |
| /* First check to see if there is any such field. */ |
| for (field = TYPE_FIELDS (type); field; field = TREE_CHAIN (field)) |
| if (TREE_CODE (field) == FIELD_DECL |
| && TREE_CODE (TREE_TYPE (field)) == REAL_TYPE |
| && TYPE_PRECISION (TREE_TYPE (field)) == BITS_PER_WORD |
| && host_integerp (bit_position (field), 0) |
| && int_bit_position (field) % BITS_PER_WORD == 0) |
| break; |
| |
| if (field != 0) |
| { |
| /* Now handle the special case by returning a PARALLEL |
| indicating where each 64 bit chunk goes. INFO.REG_WORDS |
| chunks are passed in registers. */ |
| unsigned int i; |
| HOST_WIDE_INT bitpos; |
| rtx ret; |
| |
| /* assign_parms checks the mode of ENTRY_PARM, so we must |
| use the actual mode here. */ |
| ret = gen_rtx_PARALLEL (mode, rtvec_alloc (info.reg_words)); |
| |
| bitpos = 0; |
| field = TYPE_FIELDS (type); |
| for (i = 0; i < info.reg_words; i++) |
| { |
| rtx reg; |
| |
| for (; field; field = TREE_CHAIN (field)) |
| if (TREE_CODE (field) == FIELD_DECL |
| && int_bit_position (field) >= bitpos) |
| break; |
| |
| if (field |
| && int_bit_position (field) == bitpos |
| && TREE_CODE (TREE_TYPE (field)) == REAL_TYPE |
| && !TARGET_SOFT_FLOAT |
| && TYPE_PRECISION (TREE_TYPE (field)) == BITS_PER_WORD) |
| reg = gen_rtx_REG (DFmode, FP_ARG_FIRST + info.reg_offset + i); |
| else |
| reg = gen_rtx_REG (DImode, GP_ARG_FIRST + info.reg_offset + i); |
| |
| XVECEXP (ret, 0, i) |
| = gen_rtx_EXPR_LIST (VOIDmode, reg, |
| GEN_INT (bitpos / BITS_PER_UNIT)); |
| |
| bitpos += BITS_PER_WORD; |
| } |
| return ret; |
| } |
| } |
| |
| /* Handle the n32/n64 conventions for passing complex floating-point |
| arguments in FPR pairs. The real part goes in the lower register |
| and the imaginary part goes in the upper register. */ |
| if (TARGET_NEWABI |
| && info.fpr_p |
| && GET_MODE_CLASS (mode) == MODE_COMPLEX_FLOAT) |
| { |
| rtx real, imag; |
| enum machine_mode inner; |
| int reg; |
| |
| inner = GET_MODE_INNER (mode); |
| reg = FP_ARG_FIRST + info.reg_offset; |
| real = gen_rtx_EXPR_LIST (VOIDmode, |
| gen_rtx_REG (inner, reg), |
| const0_rtx); |
| imag = gen_rtx_EXPR_LIST (VOIDmode, |
| gen_rtx_REG (inner, reg + info.reg_words / 2), |
| GEN_INT (GET_MODE_SIZE (inner))); |
| return gen_rtx_PARALLEL (mode, gen_rtvec (2, real, imag)); |
| } |
| |
| if (!info.fpr_p) |
| return gen_rtx_REG (mode, GP_ARG_FIRST + info.reg_offset); |
| else if (info.reg_offset == 1) |
| /* This code handles the special o32 case in which the second word |
| of the argument structure is passed in floating-point registers. */ |
| return gen_rtx_REG (mode, FP_ARG_FIRST + FP_INC); |
| else |
| return gen_rtx_REG (mode, FP_ARG_FIRST + info.reg_offset); |
| } |
| |
| |
| /* Implement TARGET_ARG_PARTIAL_BYTES. */ |
| |
| static int |
| mips_arg_partial_bytes (CUMULATIVE_ARGS *cum, |
| enum machine_mode mode, tree type, bool named) |
| { |
| struct mips_arg_info info; |
| |
| mips_arg_info (cum, mode, type, named, &info); |
| return info.stack_words > 0 ? info.reg_words * UNITS_PER_WORD : 0; |
| } |
| |
| |
| /* Implement FUNCTION_ARG_BOUNDARY. Every parameter gets at least |
| PARM_BOUNDARY bits of alignment, but will be given anything up |
| to STACK_BOUNDARY bits if the type requires it. */ |
| |
| int |
| function_arg_boundary (enum machine_mode mode, tree type) |
| { |
| unsigned int alignment; |
| |
| alignment = type ? TYPE_ALIGN (type) : GET_MODE_ALIGNMENT (mode); |
| if (alignment < PARM_BOUNDARY) |
| alignment = PARM_BOUNDARY; |
| if (alignment > STACK_BOUNDARY) |
| alignment = STACK_BOUNDARY; |
| return alignment; |
| } |
| |
| /* Return true if FUNCTION_ARG_PADDING (MODE, TYPE) should return |
| upward rather than downward. In other words, return true if the |
| first byte of the stack slot has useful data, false if the last |
| byte does. */ |
| |
| bool |
| mips_pad_arg_upward (enum machine_mode mode, tree type) |
| { |
| /* On little-endian targets, the first byte of every stack argument |
| is passed in the first byte of the stack slot. */ |
| if (!BYTES_BIG_ENDIAN) |
| return true; |
| |
| /* Otherwise, integral types are padded downward: the last byte of a |
| stack argument is passed in the last byte of the stack slot. */ |
| if (type != 0 |
| ? INTEGRAL_TYPE_P (type) || POINTER_TYPE_P (type) |
| : GET_MODE_CLASS (mode) == MODE_INT) |
| return false; |
| |
| /* Big-endian o64 pads floating-point arguments downward. */ |
| if (mips_abi == ABI_O64) |
| if (type != 0 ? FLOAT_TYPE_P (type) : GET_MODE_CLASS (mode) == MODE_FLOAT) |
| return false; |
| |
| /* Other types are padded upward for o32, o64, n32 and n64. */ |
| if (mips_abi != ABI_EABI) |
| return true; |
| |
| /* Arguments smaller than a stack slot are padded downward. */ |
| if (mode != BLKmode) |
| return (GET_MODE_BITSIZE (mode) >= PARM_BOUNDARY); |
| else |
| return (int_size_in_bytes (type) >= (PARM_BOUNDARY / BITS_PER_UNIT)); |
| } |
| |
| |
| /* Likewise BLOCK_REG_PADDING (MODE, TYPE, ...). Return !BYTES_BIG_ENDIAN |
| if the least significant byte of the register has useful data. Return |
| the opposite if the most significant byte does. */ |
| |
| bool |
| mips_pad_reg_upward (enum machine_mode mode, tree type) |
| { |
| /* No shifting is required for floating-point arguments. */ |
| if (type != 0 ? FLOAT_TYPE_P (type) : GET_MODE_CLASS (mode) == MODE_FLOAT) |
| return !BYTES_BIG_ENDIAN; |
| |
| /* Otherwise, apply the same padding to register arguments as we do |
| to stack arguments. */ |
| return mips_pad_arg_upward (mode, type); |
| } |
| |
| static void |
| mips_setup_incoming_varargs (CUMULATIVE_ARGS *cum, enum machine_mode mode, |
| tree type, int *pretend_size, int no_rtl) |
| { |
| CUMULATIVE_ARGS local_cum; |
| int gp_saved, fp_saved; |
| |
| /* The caller has advanced CUM up to, but not beyond, the last named |
| argument. Advance a local copy of CUM past the last "real" named |
| argument, to find out how many registers are left over. */ |
| |
| local_cum = *cum; |
| FUNCTION_ARG_ADVANCE (local_cum, mode, type, 1); |
| |
| /* Found out how many registers we need to save. */ |
| gp_saved = MAX_ARGS_IN_REGISTERS - local_cum.num_gprs; |
| fp_saved = (EABI_FLOAT_VARARGS_P |
| ? MAX_ARGS_IN_REGISTERS - local_cum.num_fprs |
| : 0); |
| |
| if (!no_rtl) |
| { |
| if (gp_saved > 0) |
| { |
| rtx ptr, mem; |
| |
| ptr = virtual_incoming_args_rtx; |
| switch (mips_abi) |
| { |
| case ABI_32: |
| case ABI_O64: |
| ptr = plus_constant (ptr, local_cum.num_gprs * UNITS_PER_WORD); |
| break; |
| |
| case ABI_EABI: |
| ptr = plus_constant (ptr, -gp_saved * UNITS_PER_WORD); |
| break; |
| } |
| mem = gen_rtx_MEM (BLKmode, ptr); |
| set_mem_alias_set (mem, get_varargs_alias_set ()); |
| |
| move_block_from_reg (local_cum.num_gprs + GP_ARG_FIRST, |
| mem, gp_saved); |
| } |
| if (fp_saved > 0) |
| { |
| /* We can't use move_block_from_reg, because it will use |
| the wrong mode. */ |
| enum machine_mode mode; |
| int off, i; |
| |
| /* Set OFF to the offset from virtual_incoming_args_rtx of |
| the first float register. The FP save area lies below |
| the integer one, and is aligned to UNITS_PER_FPVALUE bytes. */ |
| off = -gp_saved * UNITS_PER_WORD; |
| off &= ~(UNITS_PER_FPVALUE - 1); |
| off -= fp_saved * UNITS_PER_FPREG; |
| |
| mode = TARGET_SINGLE_FLOAT ? SFmode : DFmode; |
| |
| for (i = local_cum.num_fprs; i < MAX_ARGS_IN_REGISTERS; i += FP_INC) |
| { |
| rtx ptr, mem; |
| |
| ptr = plus_constant (virtual_incoming_args_rtx, off); |
| mem = gen_rtx_MEM (mode, ptr); |
| set_mem_alias_set (mem, get_varargs_alias_set ()); |
| emit_move_insn (mem, gen_rtx_REG (mode, FP_ARG_FIRST + i)); |
| off += UNITS_PER_HWFPVALUE; |
| } |
| } |
| } |
| if (TARGET_OLDABI) |
| { |
| /* No need for pretend arguments: the register parameter area was |
| allocated by the caller. */ |
| *pretend_size = 0; |
| return; |
| } |
| *pretend_size = (gp_saved * UNITS_PER_WORD) + (fp_saved * UNITS_PER_FPREG); |
| } |
| |
| /* Create the va_list data type. |
| We keep 3 pointers, and two offsets. |
| Two pointers are to the overflow area, which starts at the CFA. |
| One of these is constant, for addressing into the GPR save area below it. |
| The other is advanced up the stack through the overflow region. |
| The third pointer is to the GPR save area. Since the FPR save area |
| is just below it, we can address FPR slots off this pointer. |
| We also keep two one-byte offsets, which are to be subtracted from the |
| constant pointers to yield addresses in the GPR and FPR save areas. |
| These are downcounted as float or non-float arguments are used, |
| and when they get to zero, the argument must be obtained from the |
| overflow region. |
| If !EABI_FLOAT_VARARGS_P, then no FPR save area exists, and a single |
| pointer is enough. It's started at the GPR save area, and is |
| advanced, period. |
| Note that the GPR save area is not constant size, due to optimization |
| in the prologue. Hence, we can't use a design with two pointers |
| and two offsets, although we could have designed this with two pointers |
| and three offsets. */ |
| |
| static tree |
| mips_build_builtin_va_list (void) |
| { |
| if (EABI_FLOAT_VARARGS_P) |
| { |
| tree f_ovfl, f_gtop, f_ftop, f_goff, f_foff, f_res, record; |
| tree array, index; |
| |
| record = (*lang_hooks.types.make_type) (RECORD_TYPE); |
| |
| f_ovfl = build_decl (FIELD_DECL, get_identifier ("__overflow_argptr"), |
| ptr_type_node); |
| f_gtop = build_decl (FIELD_DECL, get_identifier ("__gpr_top"), |
| ptr_type_node); |
| f_ftop = build_decl (FIELD_DECL, get_identifier ("__fpr_top"), |
| ptr_type_node); |
| f_goff = build_decl (FIELD_DECL, get_identifier ("__gpr_offset"), |
| unsigned_char_type_node); |
| f_foff = build_decl (FIELD_DECL, get_identifier ("__fpr_offset"), |
| unsigned_char_type_node); |
| /* Explicitly pad to the size of a pointer, so that -Wpadded won't |
| warn on every user file. */ |
| index = build_int_cst (NULL_TREE, GET_MODE_SIZE (ptr_mode) - 2 - 1); |
| array = build_array_type (unsigned_char_type_node, |
| build_index_type (index)); |
| f_res = build_decl (FIELD_DECL, get_identifier ("__reserved"), array); |
| |
| DECL_FIELD_CONTEXT (f_ovfl) = record; |
| DECL_FIELD_CONTEXT (f_gtop) = record; |
| DECL_FIELD_CONTEXT (f_ftop) = record; |
| DECL_FIELD_CONTEXT (f_goff) = record; |
| DECL_FIELD_CONTEXT (f_foff) = record; |
| DECL_FIELD_CONTEXT (f_res) = record; |
| |
| TYPE_FIELDS (record) = f_ovfl; |
| TREE_CHAIN (f_ovfl) = f_gtop; |
| TREE_CHAIN (f_gtop) = f_ftop; |
| TREE_CHAIN (f_ftop) = f_goff; |
| TREE_CHAIN (f_goff) = f_foff; |
| TREE_CHAIN (f_foff) = f_res; |
| |
| layout_type (record); |
| return record; |
| } |
| else if (TARGET_IRIX && TARGET_IRIX6) |
| /* On IRIX 6, this type is 'char *'. */ |
| return build_pointer_type (char_type_node); |
| else |
| /* Otherwise, we use 'void *'. */ |
| return ptr_type_node; |
| } |
| |
| /* Implement va_start. */ |
| |
| void |
| mips_va_start (tree valist, rtx nextarg) |
| { |
| const CUMULATIVE_ARGS *cum = ¤t_function_args_info; |
| |
| /* ARG_POINTER_REGNUM is initialized to STACK_POINTER_BOUNDARY, but |
| since the stack is aligned for a pair of argument-passing slots, |
| and the beginning of a variable argument list may be an odd slot, |
| we have to decrease its alignment. */ |
| if (cfun && cfun->emit->regno_pointer_align) |
| while (((current_function_pretend_args_size * BITS_PER_UNIT) |
| & (REGNO_POINTER_ALIGN (ARG_POINTER_REGNUM) - 1)) != 0) |
| REGNO_POINTER_ALIGN (ARG_POINTER_REGNUM) /= 2; |
| |
| if (mips_abi == ABI_EABI) |
| { |
| int gpr_save_area_size; |
| |
| gpr_save_area_size |
| = (MAX_ARGS_IN_REGISTERS - cum->num_gprs) * UNITS_PER_WORD; |
| |
| if (EABI_FLOAT_VARARGS_P) |
| { |
| tree f_ovfl, f_gtop, f_ftop, f_goff, f_foff; |
| tree ovfl, gtop, ftop, goff, foff; |
| tree t; |
| int fpr_offset; |
| int fpr_save_area_size; |
| |
| f_ovfl = TYPE_FIELDS (va_list_type_node); |
| f_gtop = TREE_CHAIN (f_ovfl); |
| f_ftop = TREE_CHAIN (f_gtop); |
| f_goff = TREE_CHAIN (f_ftop); |
| f_foff = TREE_CHAIN (f_goff); |
| |
| ovfl = build (COMPONENT_REF, TREE_TYPE (f_ovfl), valist, f_ovfl, |
| NULL_TREE); |
| gtop = build (COMPONENT_REF, TREE_TYPE (f_gtop), valist, f_gtop, |
| NULL_TREE); |
| ftop = build (COMPONENT_REF, TREE_TYPE (f_ftop), valist, f_ftop, |
| NULL_TREE); |
| goff = build (COMPONENT_REF, TREE_TYPE (f_goff), valist, f_goff, |
| NULL_TREE); |
| foff = build (COMPONENT_REF, TREE_TYPE (f_foff), valist, f_foff, |
| NULL_TREE); |
| |
| /* Emit code to initialize OVFL, which points to the next varargs |
| stack argument. CUM->STACK_WORDS gives the number of stack |
| words used by named arguments. */ |
| t = make_tree (TREE_TYPE (ovfl), virtual_incoming_args_rtx); |
| if (cum->stack_words > 0) |
| t = build (PLUS_EXPR, TREE_TYPE (ovfl), t, |
| build_int_cst (NULL_TREE, |
| cum->stack_words * UNITS_PER_WORD)); |
| t = build (MODIFY_EXPR, TREE_TYPE (ovfl), ovfl, t); |
| expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL); |
| |
| /* Emit code to initialize GTOP, the top of the GPR save area. */ |
| t = make_tree (TREE_TYPE (gtop), virtual_incoming_args_rtx); |
| t = build (MODIFY_EXPR, TREE_TYPE (gtop), gtop, t); |
| expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL); |
| |
| /* Emit code to initialize FTOP, the top of the FPR save area. |
| This address is gpr_save_area_bytes below GTOP, rounded |
| down to the next fp-aligned boundary. */ |
| t = make_tree (TREE_TYPE (ftop), virtual_incoming_args_rtx); |
| fpr_offset = gpr_save_area_size + UNITS_PER_FPVALUE - 1; |
| fpr_offset &= ~(UNITS_PER_FPVALUE - 1); |
| if (fpr_offset) |
| t = build (PLUS_EXPR, TREE_TYPE (ftop), t, |
| build_int_cst (NULL_TREE, -fpr_offset)); |
| t = build (MODIFY_EXPR, TREE_TYPE (ftop), ftop, t); |
| expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL); |
| |
| /* Emit code to initialize GOFF, the offset from GTOP of the |
| next GPR argument. */ |
| t = build (MODIFY_EXPR, TREE_TYPE (goff), goff, |
| build_int_cst (NULL_TREE, gpr_save_area_size)); |
| expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL); |
| |
| /* Likewise emit code to initialize FOFF, the offset from FTOP |
| of the next FPR argument. */ |
| fpr_save_area_size |
| = (MAX_ARGS_IN_REGISTERS - cum->num_fprs) * UNITS_PER_FPREG; |
| t = build (MODIFY_EXPR, TREE_TYPE (foff), foff, |
| build_int_cst (NULL_TREE, fpr_save_area_size)); |
| expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL); |
| } |
| else |
| { |
| /* Everything is in the GPR save area, or in the overflow |
| area which is contiguous with it. */ |
| nextarg = plus_constant (nextarg, -gpr_save_area_size); |
| std_expand_builtin_va_start (valist, nextarg); |
| } |
| } |
| else |
| std_expand_builtin_va_start (valist, nextarg); |
| } |
| |
| /* Implement va_arg. */ |
| |
| static tree |
| mips_gimplify_va_arg_expr (tree valist, tree type, tree *pre_p, tree *post_p) |
| { |
| HOST_WIDE_INT size, rsize; |
| tree addr; |
| bool indirect; |
| |
| indirect = pass_by_reference (NULL, TYPE_MODE (type), type, 0); |
| |
| if (indirect) |
| type = build_pointer_type (type); |
| |
| size = int_size_in_bytes (type); |
| rsize = (size + UNITS_PER_WORD - 1) & -UNITS_PER_WORD; |
| |
| if (mips_abi != ABI_EABI || !EABI_FLOAT_VARARGS_P) |
| addr = std_gimplify_va_arg_expr (valist, type, pre_p, post_p); |
| else |
| { |
| /* Not a simple merged stack. */ |
| |
| tree f_ovfl, f_gtop, f_ftop, f_goff, f_foff; |
| tree ovfl, top, off, align; |
| HOST_WIDE_INT osize; |
| tree t, u; |
| |
| f_ovfl = TYPE_FIELDS (va_list_type_node); |
| f_gtop = TREE_CHAIN (f_ovfl); |
| f_ftop = TREE_CHAIN (f_gtop); |
| f_goff = TREE_CHAIN (f_ftop); |
| f_foff = TREE_CHAIN (f_goff); |
| |
| /* We maintain separate pointers and offsets for floating-point |
| and integer arguments, but we need similar code in both cases. |
| Let: |
| |
| TOP be the top of the register save area; |
| OFF be the offset from TOP of the next register; |
| ADDR_RTX be the address of the argument; |
| RSIZE be the number of bytes used to store the argument |
| when it's in the register save area; |
| OSIZE be the number of bytes used to store it when it's |
| in the stack overflow area; and |
| PADDING be (BYTES_BIG_ENDIAN ? OSIZE - RSIZE : 0) |
| |
| The code we want is: |
| |
| 1: off &= -rsize; // round down |
| 2: if (off != 0) |
| 3: { |
| 4: addr_rtx = top - off; |
| 5: off -= rsize; |
| 6: } |
| 7: else |
| 8: { |
| 9: ovfl += ((intptr_t) ovfl + osize - 1) & -osize; |
| 10: addr_rtx = ovfl + PADDING; |
| 11: ovfl += osize; |
| 14: } |
| |
| [1] and [9] can sometimes be optimized away. */ |
| |
| ovfl = build (COMPONENT_REF, TREE_TYPE (f_ovfl), valist, f_ovfl, |
| NULL_TREE); |
| |
| if (GET_MODE_CLASS (TYPE_MODE (type)) == MODE_FLOAT |
| && GET_MODE_SIZE (TYPE_MODE (type)) <= UNITS_PER_FPVALUE) |
| { |
| top = build (COMPONENT_REF, TREE_TYPE (f_ftop), valist, f_ftop, |
| NULL_TREE); |
| off = build (COMPONENT_REF, TREE_TYPE (f_foff), valist, f_foff, |
| NULL_TREE); |
| |
| /* When floating-point registers are saved to the stack, |
| each one will take up UNITS_PER_HWFPVALUE bytes, regardless |
| of the float's precision. */ |
| rsize = UNITS_PER_HWFPVALUE; |
| |
| /* Overflow arguments are padded to UNITS_PER_WORD bytes |
| (= PARM_BOUNDARY bits). This can be different from RSIZE |
| in two cases: |
| |
| (1) On 32-bit targets when TYPE is a structure such as: |
| |
| struct s { float f; }; |
| |
| Such structures are passed in paired FPRs, so RSIZE |
| will be 8 bytes. However, the structure only takes |
| up 4 bytes of memory, so OSIZE will only be 4. |
| |
| (2) In combinations such as -mgp64 -msingle-float |
| -fshort-double. Doubles passed in registers |
| will then take up 4 (UNITS_PER_HWFPVALUE) bytes, |
| but those passed on the stack take up |
| UNITS_PER_WORD bytes. */ |
| osize = MAX (GET_MODE_SIZE (TYPE_MODE (type)), UNITS_PER_WORD); |
| } |
| else |
| { |
| top = build (COMPONENT_REF, TREE_TYPE (f_gtop), valist, f_gtop, |
| NULL_TREE); |
| off = build (COMPONENT_REF, TREE_TYPE (f_goff), valist, f_goff, |
| NULL_TREE); |
| if (rsize > UNITS_PER_WORD) |
| { |
| /* [1] Emit code for: off &= -rsize. */ |
| t = build (BIT_AND_EXPR, TREE_TYPE (off), off, |
| build_int_cst (NULL_TREE, -rsize)); |
| t = build (MODIFY_EXPR, TREE_TYPE (off), off, t); |
| gimplify_and_add (t, pre_p); |
| } |
| osize = rsize; |
| } |
| |
| /* [2] Emit code to branch if off == 0. */ |
| t = lang_hooks.truthvalue_conversion (off); |
| addr = build (COND_EXPR, ptr_type_node, t, NULL, NULL); |
| |
| /* [5] Emit code for: off -= rsize. We do this as a form of |
| post-increment not available to C. Also widen for the |
| coming pointer arithmetic. */ |
| t = fold_convert (TREE_TYPE (off), build_int_cst (NULL_TREE, rsize)); |
| t = build (POSTDECREMENT_EXPR, TREE_TYPE (off), off, t); |
| t = fold_convert (sizetype, t); |
| t = fold_convert (TREE_TYPE (top), t); |
| |
| /* [4] Emit code for: addr_rtx = top - off. On big endian machines, |
| the argument has RSIZE - SIZE bytes of leading padding. */ |
| t = build (MINUS_EXPR, TREE_TYPE (top), top, t); |
| if (BYTES_BIG_ENDIAN && rsize > size) |
| { |
| u = fold_convert (TREE_TYPE (t), build_int_cst (NULL_TREE, |
| rsize - size)); |
| t = build (PLUS_EXPR, TREE_TYPE (t), t, u); |
| } |
| COND_EXPR_THEN (addr) = t; |
| |
| if (osize > UNITS_PER_WORD) |
| { |
| /* [9] Emit: ovfl += ((intptr_t) ovfl + osize - 1) & -osize. */ |
| u = fold_convert (TREE_TYPE (ovfl), |
| build_int_cst (NULL_TREE, osize - 1)); |
| t = build (PLUS_EXPR, TREE_TYPE (ovfl), ovfl, u); |
| u = fold_convert (TREE_TYPE (ovfl), |
| build_int_cst (NULL_TREE, -osize)); |
| t = build (BIT_AND_EXPR, TREE_TYPE (ovfl), t, u); |
| align = build (MODIFY_EXPR, TREE_TYPE (ovfl), ovfl, t); |
| } |
| else |
| align = NULL; |
| |
| /* [10, 11]. Emit code to store ovfl in addr_rtx, then |
| post-increment ovfl by osize. On big-endian machines, |
| the argument has OSIZE - SIZE bytes of leading padding. */ |
| u = fold_convert (TREE_TYPE (ovfl), |
| build_int_cst (NULL_TREE, osize)); |
| t = build (POSTINCREMENT_EXPR, TREE_TYPE (ovfl), ovfl, u); |
| if (BYTES_BIG_ENDIAN && osize > size) |
| { |
| u = fold_convert (TREE_TYPE (t), |
| build_int_cst (NULL_TREE, osize - size)); |
| t = build (PLUS_EXPR, TREE_TYPE (t), t, u); |
| } |
| |
| /* String [9] and [10,11] together. */ |
| if (align) |
| t = build (COMPOUND_EXPR, TREE_TYPE (t), align, t); |
| COND_EXPR_ELSE (addr) = t; |
| |
| addr = fold_convert (build_pointer_type (type), addr); |
| addr = build_fold_indirect_ref (addr); |
| } |
| |
| if (indirect) |
| addr = build_fold_indirect_ref (addr); |
| |
| return addr; |
| } |
| |
| /* Return true if it is possible to use left/right accesses for a |
| bitfield of WIDTH bits starting BITPOS bits into *OP. When |
| returning true, update *OP, *LEFT and *RIGHT as follows: |
| |
| *OP is a BLKmode reference to the whole field. |
| |
| *LEFT is a QImode reference to the first byte if big endian or |
| the last byte if little endian. This address can be used in the |
| left-side instructions (lwl, swl, ldl, sdl). |
| |
| *RIGHT is a QImode reference to the opposite end of the field and |
| can be used in the parterning right-side instruction. */ |
| |
| static bool |
| mips_get_unaligned_mem (rtx *op, unsigned int width, int bitpos, |
| rtx *left, rtx *right) |
| { |
| rtx first, last; |
| |
| /* Check that the operand really is a MEM. Not all the extv and |
| extzv predicates are checked. */ |
| if (!MEM_P (*op)) |
| return false; |
| |
| /* Check that the size is valid. */ |
| if (width != 32 && (!TARGET_64BIT || width != 64)) |
| return false; |
| |
| /* We can only access byte-aligned values. Since we are always passed |
| a reference to the first byte of the field, it is not necessary to |
| do anything with BITPOS after this check. */ |
| if (bitpos % BITS_PER_UNIT != 0) |
| return false; |
| |
| /* Reject aligned bitfields: we want to use a normal load or store |
| instead of a left/right pair. */ |
| if (MEM_ALIGN (*op) >= width) |
| return false; |
| |
| /* Adjust *OP to refer to the whole field. This also has the effect |
| of legitimizing *OP's address for BLKmode, possibly simplifying it. */ |
| *op = adjust_address (*op, BLKmode, 0); |
| set_mem_size (*op, GEN_INT (width / BITS_PER_UNIT)); |
| |
| /* Get references to both ends of the field. We deliberately don't |
| use the original QImode *OP for FIRST since the new BLKmode one |
| might have a simpler address. */ |
| first = adjust_address (*op, QImode, 0); |
| last = adjust_address (*op, QImode, width / BITS_PER_UNIT - 1); |
| |
| /* Allocate to LEFT and RIGHT according to endianness. LEFT should |
| be the upper word and RIGHT the lower word. */ |
| if (TARGET_BIG_ENDIAN) |
| *left = first, *right = last; |
| else |
| *left = last, *right = first; |
| |
| return true; |
| } |
| |
| |
| /* Try to emit the equivalent of (set DEST (zero_extract SRC WIDTH BITPOS)). |
| Return true on success. We only handle cases where zero_extract is |
| equivalent to sign_extract. */ |
| |
| bool |
| mips_expand_unaligned_load (rtx dest, rtx src, unsigned int width, int bitpos) |
| { |
| rtx left, right, temp; |
| |
| /* If TARGET_64BIT, the destination of a 32-bit load will be a |
| paradoxical word_mode subreg. This is the only case in which |
| we allow the destination to be larger than the source. */ |
| if (GET_CODE (dest) == SUBREG |
| && GET_MODE (dest) == DImode |
| && SUBREG_BYTE (dest) == 0 |
| && GET_MODE (SUBREG_REG (dest)) == SImode) |
| dest = SUBREG_REG (dest); |
| |
| /* After the above adjustment, the destination must be the same |
| width as the source. */ |
| if (GET_MODE_BITSIZE (GET_MODE (dest)) != width) |
| return false; |
| |
| if (!mips_get_unaligned_mem (&src, width, bitpos, &left, &right)) |
| return false; |
| |
| temp = gen_reg_rtx (GET_MODE (dest)); |
| if (GET_MODE (dest) == DImode) |
| { |
| emit_insn (gen_mov_ldl (temp, src, left)); |
| emit_insn (gen_mov_ldr (dest, copy_rtx (src), right, temp)); |
| } |
| else |
| { |
| emit_insn (gen_mov_lwl (temp, src, left)); |
| emit_insn (gen_mov_lwr (dest, copy_rtx (src), right, temp)); |
| } |
| return true; |
| } |
| |
| |
| /* Try to expand (set (zero_extract DEST WIDTH BITPOS) SRC). Return |
| true on success. */ |
| |
| bool |
| mips_expand_unaligned_store (rtx dest, rtx src, unsigned int width, int bitpos) |
| { |
| rtx left, right; |
| |
| if (!mips_get_unaligned_mem (&dest, width, bitpos, &left, &right)) |
| return false; |
| |
| src = gen_lowpart (mode_for_size (width, MODE_INT, 0), src); |
| |
| if (GET_MODE (src) == DImode) |
| { |
| emit_insn (gen_mov_sdl (dest, src, left)); |
| emit_insn (gen_mov_sdr (copy_rtx (dest), copy_rtx (src), right)); |
| } |
| else |
| { |
| emit_insn (gen_mov_swl (dest, src, left)); |
| emit_insn (gen_mov_swr (copy_rtx (dest), copy_rtx (src), right)); |
| } |
| return true; |
| } |
| |
| /* Set up globals to generate code for the ISA or processor |
| described by INFO. */ |
| |
| static void |
| mips_set_architecture (const struct mips_cpu_info *info) |
| { |
| if (info != 0) |
| { |
| mips_arch_info = info; |
| mips_arch = info->cpu; |
| mips_isa = info->isa; |
| } |
| } |
| |
| |
| /* Likewise for tuning. */ |
| |
| static void |
| mips_set_tune (const struct mips_cpu_info *info) |
| { |
| if (info != 0) |
| { |
| mips_tune_info = info; |
| mips_tune = info->cpu; |
| } |
| } |
| |
| |
| /* Set up the threshold for data to go into the small data area, instead |
| of the normal data area, and detect any conflicts in the switches. */ |
| |
| void |
| override_options (void) |
| { |
| int i, start, regno; |
| enum machine_mode mode; |
| |
| mips_section_threshold = g_switch_set ? g_switch_value : MIPS_DEFAULT_GVALUE; |
| |
| /* Interpret -mabi. */ |
| mips_abi = MIPS_ABI_DEFAULT; |
| if (mips_abi_string != 0) |
| { |
| if (strcmp (mips_abi_string, "32") == 0) |
| mips_abi = ABI_32; |
| else if (strcmp (mips_abi_string, "o64") == 0) |
| mips_abi = ABI_O64; |
| else if (strcmp (mips_abi_string, "n32") == 0) |
| mips_abi = ABI_N32; |
| else if (strcmp (mips_abi_string, "64") == 0) |
| mips_abi = ABI_64; |
| else if (strcmp (mips_abi_string, "eabi") == 0) |
| mips_abi = ABI_EABI; |
| else |
| fatal_error ("bad value (%s) for -mabi= switch", mips_abi_string); |
| } |
| |
| /* The following code determines the architecture and register size. |
| Similar code was added to GAS 2.14 (see tc-mips.c:md_after_parse_args()). |
| The GAS and GCC code should be kept in sync as much as possible. */ |
| |
| if (mips_arch_string != 0) |
| mips_set_architecture (mips_parse_cpu ("-march", mips_arch_string)); |
| |
| if (mips_isa_string != 0) |
| { |
| /* Handle -mipsN. */ |
| char *whole_isa_str = concat ("mips", mips_isa_string, NULL); |
| const struct mips_cpu_info *isa_info; |
| |
| isa_info = mips_parse_cpu ("-mips option", whole_isa_str); |
| free (whole_isa_str); |
| |
| /* -march takes precedence over -mipsN, since it is more descriptive. |
| There's no harm in specifying both as long as the ISA levels |
| are the same. */ |
| if (mips_arch_info != 0 && mips_isa != isa_info->isa) |
| error ("-mips%s conflicts with the other architecture options, " |
| "which specify a MIPS%d processor", |
| mips_isa_string, mips_isa); |
| |
| /* Set architecture based on the given option. */ |
| mips_set_architecture (isa_info); |
| } |
| |
| if (mips_arch_info == 0) |
| { |
| #ifdef MIPS_CPU_STRING_DEFAULT |
| mips_set_architecture (mips_parse_cpu ("default CPU", |
| MIPS_CPU_STRING_DEFAULT)); |
| #else |
| mips_set_architecture (mips_cpu_info_from_isa (MIPS_ISA_DEFAULT)); |
| #endif |
| } |
| |
| if (ABI_NEEDS_64BIT_REGS && !ISA_HAS_64BIT_REGS) |
| error ("-march=%s is not compatible with the selected ABI", |
| mips_arch_info->name); |
| |
| /* Optimize for mips_arch, unless -mtune selects a different processor. */ |
| if (mips_tune_string != 0) |
| mips_set_tune (mips_parse_cpu ("-mtune", mips_tune_string)); |
| |
| if (mips_tune_info == 0) |
| mips_set_tune (mips_arch_info); |
| |
| if ((target_flags_explicit & MASK_64BIT) != 0) |
| { |
| /* The user specified the size of the integer registers. Make sure |
| it agrees with the ABI and ISA. */ |
| if (TARGET_64BIT && !ISA_HAS_64BIT_REGS) |
| error ("-mgp64 used with a 32-bit processor"); |
| else if (!TARGET_64BIT && ABI_NEEDS_64BIT_REGS) |
| error ("-mgp32 used with a 64-bit ABI"); |
| else if (TARGET_64BIT && ABI_NEEDS_32BIT_REGS) |
| error ("-mgp64 used with a 32-bit ABI"); |
| } |
| else |
| { |
| /* Infer the integer register size from the ABI and processor. |
| Restrict ourselves to 32-bit registers if that's all the |
| processor has, or if the ABI cannot handle 64-bit registers. */ |
| if (ABI_NEEDS_32BIT_REGS || !ISA_HAS_64BIT_REGS) |
| target_flags &= ~MASK_64BIT; |
| else |
| target_flags |= MASK_64BIT; |
| } |
| |
| if ((target_flags_explicit & MASK_FLOAT64) != 0) |
| { |
| /* Really, -mfp32 and -mfp64 are ornamental options. There's |
| only one right answer here. */ |
| if (TARGET_64BIT && TARGET_DOUBLE_FLOAT && !TARGET_FLOAT64) |
| error ("unsupported combination: %s", "-mgp64 -mfp32 -mdouble-float"); |
| else if (!TARGET_64BIT && TARGET_FLOAT64) |
| error ("unsupported combination: %s", "-mgp32 -mfp64"); |
| else if (TARGET_SINGLE_FLOAT && TARGET_FLOAT64) |
| error ("unsupported combination: %s", "-mfp64 -msingle-float"); |
| } |
| else |
| { |
| /* -msingle-float selects 32-bit float registers. Otherwise the |
| float registers should be the same size as the integer ones. */ |
| if (TARGET_64BIT && TARGET_DOUBLE_FLOAT) |
| target_flags |= MASK_FLOAT64; |
| else |
| target_flags &= ~MASK_FLOAT64; |
| } |
| |
| /* End of code shared with GAS. */ |
| |
| if ((target_flags_explicit & MASK_LONG64) == 0) |
| { |
| /* If no type size setting options (-mlong64,-mint64,-mlong32) |
| were used, then set the type sizes. In the EABI in 64 bit mode, |
| longs and pointers are 64 bits. Likewise for the SGI Irix6 N64 |
| ABI. */ |
| if ((mips_abi == ABI_EABI && TARGET_64BIT) || mips_abi == ABI_64) |
| target_flags |= MASK_LONG64; |
| else |
| target_flags &= ~MASK_LONG64; |
| } |
| |
| /* Deprecate -mint64. Remove after 4.0 branches. */ |
| if (TARGET_INT64) |
| warning ("-mint64 is a deprecated option"); |
| |
| if (mips_fix_vr4130_string && mips_fix_vr4130_string[0] != 0) |
| error ("unrecognized option %<-mfix-vr4130%s%>", mips_fix_vr4130_string); |
| |
| if (MIPS_MARCH_CONTROLS_SOFT_FLOAT |
| && (target_flags_explicit & MASK_SOFT_FLOAT) == 0) |
| { |
| /* For some configurations, it is useful to have -march control |
| the default setting of MASK_SOFT_FLOAT. */ |
| switch ((int) mips_arch) |
| { |
| case PROCESSOR_R4100: |
| case PROCESSOR_R4111: |
| case PROCESSOR_R4120: |
| case PROCESSOR_R4130: |
| target_flags |= MASK_SOFT_FLOAT; |
| break; |
| |
| default: |
| target_flags &= ~MASK_SOFT_FLOAT; |
| break; |
| } |
| } |
| |
| if (!TARGET_OLDABI) |
| flag_pcc_struct_return = 0; |
| |
| if ((target_flags_explicit & MASK_BRANCHLIKELY) == 0) |
| { |
| /* If neither -mbranch-likely nor -mno-branch-likely was given |
| on the command line, set MASK_BRANCHLIKELY based on the target |
| architecture. |
| |
| By default, we enable use of Branch Likely instructions on |
| all architectures which support them with the following |
| exceptions: when creating MIPS32 or MIPS64 code, and when |
| tuning for architectures where their use tends to hurt |
| performance. |
| |
| The MIPS32 and MIPS64 architecture specifications say "Software |
| is strongly encouraged to avoid use of Branch Likely |
| instructions, as they will be removed from a future revision |
| of the [MIPS32 and MIPS64] architecture." Therefore, we do not |
| issue those instructions unless instructed to do so by |
| -mbranch-likely. */ |
| if (ISA_HAS_BRANCHLIKELY |
| && !(ISA_MIPS32 || ISA_MIPS32R2 || ISA_MIPS64) |
| && !(TUNE_MIPS5500 || TUNE_SB1)) |
| target_flags |= MASK_BRANCHLIKELY; |
| else |
| target_flags &= ~MASK_BRANCHLIKELY; |
| } |
| if (TARGET_BRANCHLIKELY && !ISA_HAS_BRANCHLIKELY) |
| warning ("generation of Branch Likely instructions enabled, but not supported by architecture"); |
| |
| /* The effect of -mabicalls isn't defined for the EABI. */ |
| if (mips_abi == ABI_EABI && TARGET_ABICALLS) |
| { |
| error ("unsupported combination: %s", "-mabicalls -mabi=eabi"); |
| target_flags &= ~MASK_ABICALLS; |
| } |
| |
| /* -fpic (-KPIC) is the default when TARGET_ABICALLS is defined. We need |
| to set flag_pic so that the LEGITIMATE_PIC_OPERAND_P macro will work. */ |
| /* ??? -non_shared turns off pic code generation, but this is not |
| implemented. */ |
| if (TARGET_ABICALLS) |
| { |
| flag_pic = 1; |
| if (mips_section_threshold > 0) |
| warning ("-G is incompatible with PIC code which is the default"); |
| } |
| |
| /* mips_split_addresses is a half-way house between explicit |
| relocations and the traditional assembler macros. It can |
| split absolute 32-bit symbolic constants into a high/lo_sum |
| pair but uses macros for other sorts of access. |
| |
| Like explicit relocation support for REL targets, it relies |
| on GNU extensions in the assembler and the linker. |
| |
| Although this code should work for -O0, it has traditionally |
| been treated as an optimization. */ |
| if (!TARGET_MIPS16 && TARGET_SPLIT_ADDRESSES |
| && optimize && !flag_pic |
| && !ABI_HAS_64BIT_SYMBOLS) |
| mips_split_addresses = 1; |
| else |
| mips_split_addresses = 0; |
| |
| /* -mvr4130-align is a "speed over size" optimization: it usually produces |
| faster code, but at the expense of more nops. Enable it at -O3 and |
| above. */ |
| if (optimize > 2 && (target_flags_explicit & MASK_VR4130_ALIGN) == 0) |
| target_flags |= MASK_VR4130_ALIGN; |
| |
| /* When compiling for the mips16, we cannot use floating point. We |
| record the original hard float value in mips16_hard_float. */ |
| if (TARGET_MIPS16) |
| { |
| if (TARGET_SOFT_FLOAT) |
| mips16_hard_float = 0; |
| else |
| mips16_hard_float = 1; |
| target_flags |= MASK_SOFT_FLOAT; |
| |
| /* Don't run the scheduler before reload, since it tends to |
| increase register pressure. */ |
| flag_schedule_insns = 0; |
| |
| /* Don't do hot/cold partitioning. The constant layout code expects |
| the whole function to be in a single section. */ |
| flag_reorder_blocks_and_partition = 0; |
| |
| /* Silently disable -mexplicit-relocs since it doesn't apply |
| to mips16 code. Even so, it would overly pedantic to warn |
| about "-mips16 -mexplicit-relocs", especially given that |
| we use a %gprel() operator. */ |
| target_flags &= ~MASK_EXPLICIT_RELOCS; |
| } |
| |
| /* When using explicit relocs, we call dbr_schedule from within |
| mips_reorg. */ |
| if (TARGET_EXPLICIT_RELOCS) |
| { |
| mips_flag_delayed_branch = flag_delayed_branch; |
| flag_delayed_branch = 0; |
| } |
| |
| #ifdef MIPS_TFMODE_FORMAT |
| REAL_MODE_FORMAT (TFmode) = &MIPS_TFMODE_FORMAT; |
| #endif |
| |
| /* Make sure that the user didn't turn off paired single support when |
| MIPS-3D support is requested. */ |
| if (TARGET_MIPS3D && (target_flags_explicit & MASK_PAIRED_SINGLE) |
| && !TARGET_PAIRED_SINGLE_FLOAT) |
| error ("-mips3d requires -mpaired-single"); |
| |
| /* If TARGET_MIPS3D, enable MASK_PAIRED_SINGLE. */ |
| if (TARGET_MIPS3D) |
| target_flags |= MASK_PAIRED_SINGLE; |
| |
| /* Make sure that when TARGET_PAIRED_SINGLE_FLOAT is true, TARGET_FLOAT64 |
| and TARGET_HARD_FLOAT are both true. */ |
| if (TARGET_PAIRED_SINGLE_FLOAT && !(TARGET_FLOAT64 && TARGET_HARD_FLOAT)) |
| error ("-mips3d/-mpaired-single must be used with -mfp64 -mhard-float"); |
| |
| /* Make sure that the ISA supports TARGET_PAIRED_SINGLE_FLOAT when it is |
| enabled. */ |
| if (TARGET_PAIRED_SINGLE_FLOAT && !ISA_MIPS64) |
| error ("-mips3d/-mpaired-single must be used with -mips64"); |
| |
| mips_print_operand_punct['?'] = 1; |
| mips_print_operand_punct['#'] = 1; |
| mips_print_operand_punct['/'] = 1; |
| mips_print_operand_punct['&'] = 1; |
| mips_print_operand_punct['!'] = 1; |
| mips_print_operand_punct['*'] = 1; |
| mips_print_operand_punct['@'] = 1; |
| mips_print_operand_punct['.'] = 1; |
| mips_print_operand_punct['('] = 1; |
| mips_print_operand_punct[')'] = 1; |
| mips_print_operand_punct['['] = 1; |
| mips_print_operand_punct[']'] = 1; |
| mips_print_operand_punct['<'] = 1; |
| mips_print_operand_punct['>'] = 1; |
| mips_print_operand_punct['{'] = 1; |
| mips_print_operand_punct['}'] = 1; |
| mips_print_operand_punct['^'] = 1; |
| mips_print_operand_punct['$'] = 1; |
| mips_print_operand_punct['+'] = 1; |
| mips_print_operand_punct['~'] = 1; |
| |
| mips_char_to_class['d'] = TARGET_MIPS16 ? M16_REGS : GR_REGS; |
| mips_char_to_class['t'] = T_REG; |
| mips_char_to_class['f'] = (TARGET_HARD_FLOAT ? FP_REGS : NO_REGS); |
| mips_char_to_class['h'] = HI_REG; |
| mips_char_to_class['l'] = LO_REG; |
| mips_char_to_class['x'] = MD_REGS; |
| mips_char_to_class['b'] = ALL_REGS; |
| mips_char_to_class['c'] = (TARGET_ABICALLS ? PIC_FN_ADDR_REG : |
| TARGET_MIPS16 ? M16_NA_REGS : |
| GR_REGS); |
| mips_char_to_class['e'] = LEA_REGS; |
| mips_char_to_class['j'] = PIC_FN_ADDR_REG; |
| mips_char_to_class['y'] = GR_REGS; |
| mips_char_to_class['z'] = ST_REGS; |
| mips_char_to_class['B'] = COP0_REGS; |
| mips_char_to_class['C'] = COP2_REGS; |
| mips_char_to_class['D'] = COP3_REGS; |
| |
| /* Set up array to map GCC register number to debug register number. |
| Ignore the special purpose register numbers. */ |
| |
| for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) |
| mips_dbx_regno[i] = -1; |
| |
| start = GP_DBX_FIRST - GP_REG_FIRST; |
| for (i = GP_REG_FIRST; i <= GP_REG_LAST; i++) |
| mips_dbx_regno[i] = i + start; |
| |
| start = FP_DBX_FIRST - FP_REG_FIRST; |
| for (i = FP_REG_FIRST; i <= FP_REG_LAST; i++) |
| mips_dbx_regno[i] = i + start; |
| |
| mips_dbx_regno[HI_REGNUM] = MD_DBX_FIRST + 0; |
| mips_dbx_regno[LO_REGNUM] = MD_DBX_FIRST + 1; |
| |
| /* Set up array giving whether a given register can hold a given mode. */ |
| |
| for (mode = VOIDmode; |
| mode != MAX_MACHINE_MODE; |
| mode = (enum machine_mode) ((int)mode + 1)) |
| { |
| register int size = GET_MODE_SIZE (mode); |
| register enum mode_class class = GET_MODE_CLASS (mode); |
| |
| for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++) |
| { |
| register int temp; |
| |
| if (mode == CCV2mode) |
| temp = (ISA_HAS_8CC |
| && ST_REG_P (regno) |
| && (regno - ST_REG_FIRST) % 2 == 0); |
| |
| else if (mode == CCV4mode) |
| temp = (ISA_HAS_8CC |
| && ST_REG_P (regno) |
| && (regno - ST_REG_FIRST) % 4 == 0); |
| |
| else if (mode == CCmode) |
| { |
| if (! ISA_HAS_8CC) |
| temp = (regno == FPSW_REGNUM); |
| else |
| temp = (ST_REG_P (regno) || GP_REG_P (regno) |
| || FP_REG_P (regno)); |
| } |
| |
| else if (GP_REG_P (regno)) |
| temp = ((regno & 1) == 0 || size <= UNITS_PER_WORD); |
| |
| else if (FP_REG_P (regno)) |
| temp = ((regno % FP_INC) == 0) |
| && (((class == MODE_FLOAT || class == MODE_COMPLEX_FLOAT |
| || class == MODE_VECTOR_FLOAT) |
| && size <= UNITS_PER_FPVALUE) |
| /* Allow integer modes that fit into a single |
| register. We need to put integers into FPRs |
| when using instructions like cvt and trunc. */ |
| || (class == MODE_INT && size <= UNITS_PER_FPREG) |
| /* Allow TFmode for CCmode reloads. */ |
| || (ISA_HAS_8CC && mode == TFmode)); |
| |
| else if (MD_REG_P (regno)) |
| temp = (INTEGRAL_MODE_P (mode) |
| && (size <= UNITS_PER_WORD |
| || (regno == MD_REG_FIRST |
| && size == 2 * UNITS_PER_WORD))); |
| |
| else if (ALL_COP_REG_P (regno)) |
| temp = (class == MODE_INT && size <= UNITS_PER_WORD); |
| else |
| temp = 0; |
| |
| mips_hard_regno_mode_ok[(int)mode][regno] = temp; |
| } |
| } |
| |
| /* Save GPR registers in word_mode sized hunks. word_mode hasn't been |
| initialized yet, so we can't use that here. */ |
| gpr_mode = TARGET_64BIT ? DImode : SImode; |
| |
| /* Provide default values for align_* for 64-bit targets. */ |
| if (TARGET_64BIT && !TARGET_MIPS16) |
| { |
| if (align_loops == 0) |
| align_loops = 8; |
| if (align_jumps == 0) |
| align_jumps = 8; |
| if (align_functions == 0) |
| align_functions = 8; |
| } |
| |
| /* Function to allocate machine-dependent function status. */ |
| init_machine_status = &mips_init_machine_status; |
| |
| if (ABI_HAS_64BIT_SYMBOLS) |
| { |
| if (TARGET_EXPLICIT_RELOCS) |
| { |
| mips_split_p[SYMBOL_64_HIGH] = true; |
| mips_hi_relocs[SYMBOL_64_HIGH] = "%highest("; |
| mips_lo_relocs[SYMBOL_64_HIGH] = "%higher("; |
| |
| mips_split_p[SYMBOL_64_MID] = true; |
| mips_hi_relocs[SYMBOL_64_MID] = "%higher("; |
| mips_lo_relocs[SYMBOL_64_MID] = "%hi("; |
| |
| mips_split_p[SYMBOL_64_LOW] = true; |
| mips_hi_relocs[SYMBOL_64_LOW] = "%hi("; |
| mips_lo_relocs[SYMBOL_64_LOW] = "%lo("; |
| |
| mips_split_p[SYMBOL_GENERAL] = true; |
| mips_lo_relocs[SYMBOL_GENERAL] = "%lo("; |
| } |
| } |
| else |
| { |
| if (TARGET_EXPLICIT_RELOCS || mips_split_addresses) |
| { |
| mips_split_p[SYMBOL_GENERAL] = true; |
| mips_hi_relocs[SYMBOL_GENERAL] = "%hi("; |
| mips_lo_relocs[SYMBOL_GENERAL] = "%lo("; |
| } |
| } |
| |
| if (TARGET_MIPS16) |
| { |
| /* The high part is provided by a pseudo copy of $gp. */ |
| mips_split_p[SYMBOL_SMALL_DATA] = true; |
| mips_lo_relocs[SYMBOL_SMALL_DATA] = "%gprel("; |
| } |
| |
| if (TARGET_EXPLICIT_RELOCS) |
| { |
| /* Small data constants are kept whole until after reload, |
| then lowered by mips_rewrite_small_data. */ |
| mips_lo_relocs[SYMBOL_SMALL_DATA] = "%gp_rel("; |
| |
| mips_split_p[SYMBOL_GOT_LOCAL] = true; |
| if (TARGET_NEWABI) |
| { |
| mips_lo_relocs[SYMBOL_GOTOFF_PAGE] = "%got_page("; |
| mips_lo_relocs[SYMBOL_GOT_LOCAL] = "%got_ofst("; |
| } |
| else |
| { |
| mips_lo_relocs[SYMBOL_GOTOFF_PAGE] = "%got("; |
| mips_lo_relocs[SYMBOL_GOT_LOCAL] = "%lo("; |
| } |
| |
| if (TARGET_XGOT) |
| { |
| /* The HIGH and LO_SUM are matched by special .md patterns. */ |
| mips_split_p[SYMBOL_GOT_GLOBAL] = true; |
| |
| mips_split_p[SYMBOL_GOTOFF_GLOBAL] = true; |
| mips_hi_relocs[SYMBOL_GOTOFF_GLOBAL] = "%got_hi("; |
| mips_lo_relocs[SYMBOL_GOTOFF_GLOBAL] = "%got_lo("; |
| |
| mips_split_p[SYMBOL_GOTOFF_CALL] = true; |
| mips_hi_relocs[SYMBOL_GOTOFF_CALL] = "%call_hi("; |
| mips_lo_relocs[SYMBOL_GOTOFF_CALL] = "%call_lo("; |
| } |
| else |
| { |
| if (TARGET_NEWABI) |
| mips_lo_relocs[SYMBOL_GOTOFF_GLOBAL] = "%got_disp("; |
| else |
| mips_lo_relocs[SYMBOL_GOTOFF_GLOBAL] = "%got("; |
| mips_lo_relocs[SYMBOL_GOTOFF_CALL] = "%call16("; |
| } |
| } |
| |
| if (TARGET_NEWABI) |
| { |
| mips_split_p[SYMBOL_GOTOFF_LOADGP] = true; |
| mips_hi_relocs[SYMBOL_GOTOFF_LOADGP] = "%hi(%neg(%gp_rel("; |
| mips_lo_relocs[SYMBOL_GOTOFF_LOADGP] = "%lo(%neg(%gp_rel("; |
| } |
| |
| /* Default to working around R4000 errata only if the processor |
| was selected explicitly. */ |
| if ((target_flags_explicit & MASK_FIX_R4000) == 0 |
| && mips_matching_cpu_name_p (mips_arch_info->name, "r4000")) |
| target_flags |= MASK_FIX_R4000; |
| |
| /* Default to working around R4400 errata only if the processor |
| was selected explicitly. */ |
| if ((target_flags_explicit & MASK_FIX_R4400) == 0 |
| && mips_matching_cpu_name_p (mips_arch_info->name, "r4400")) |
| target_flags |= MASK_FIX_R4400; |
| } |
| |
| /* Implement CONDITIONAL_REGISTER_USAGE. */ |
| |
| void |
| mips_conditional_register_usage (void) |
| { |
| if (!TARGET_HARD_FLOAT) |
| { |
| int regno; |
| |
| for (regno = FP_REG_FIRST; regno <= FP_REG_LAST; regno++) |
| fixed_regs[regno] = call_used_regs[regno] = 1; |
| for (regno = ST_REG_FIRST; regno <= ST_REG_LAST; regno++) |
| fixed_regs[regno] = call_used_regs[regno] = 1; |
| } |
| else if (! ISA_HAS_8CC) |
| { |
| int regno; |
| |
| /* We only have a single condition code register. We |
| implement this by hiding all the condition code registers, |
| and generating RTL that refers directly to ST_REG_FIRST. */ |
| for (regno = ST_REG_FIRST; regno <= ST_REG_LAST; regno++) |
| fixed_regs[regno] = call_used_regs[regno] = 1; |
| } |
| /* In mips16 mode, we permit the $t temporary registers to be used |
| for reload. We prohibit the unused $s registers, since they |
| are caller saved, and saving them via a mips16 register would |
| probably waste more time than just reloading the value. */ |
| if (TARGET_MIPS16) |
| { |
| fixed_regs[18] = call_used_regs[18] = 1; |
| fixed_regs[19] = call_used_regs[19] = 1; |
| fixed_regs[20] = call_used_regs[20] = 1; |
| fixed_regs[21] = call_used_regs[21] = 1; |
| fixed_regs[22] = call_used_regs[22] = 1; |
| fixed_regs[23] = call_used_regs[23] = 1; |
| fixed_regs[26] = call_used_regs[26] = 1; |
| fixed_regs[27] = call_used_regs[27] = 1; |
| fixed_regs[30] = call_used_regs[30] = 1; |
| } |
| /* fp20-23 are now caller saved. */ |
| if (mips_abi == ABI_64) |
| { |
| int regno; |
| for (regno = FP_REG_FIRST + 20; regno < FP_REG_FIRST + 24; regno++) |
| call_really_used_regs[regno] = call_used_regs[regno] = 1; |
| } |
| /* Odd registers from fp21 to fp31 are now caller saved. */ |
| if (mips_abi == ABI_N32) |
| { |
| int regno; |
| for (regno = FP_REG_FIRST + 21; regno <= FP_REG_FIRST + 31; regno+=2) |
| call_really_used_regs[regno] = call_used_regs[regno] = 1; |
| } |
| } |
| |
| /* Allocate a chunk of memory for per-function machine-dependent data. */ |
| static struct machine_function * |
| mips_init_machine_status (void) |
| { |
| return ((struct machine_function *) |
| ggc_alloc_cleared (sizeof (struct machine_function))); |
| } |
| |
| /* On the mips16, we want to allocate $24 (T_REG) before other |
| registers for instructions for which it is possible. This helps |
| avoid shuffling registers around in order to set up for an xor, |
| encouraging the compiler to use a cmp instead. */ |
| |
| void |
| mips_order_regs_for_local_alloc (void) |
| { |
| register int i; |
| |
| for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) |
| reg_alloc_order[i] = i; |
| |
| if (TARGET_MIPS16) |
| { |
| /* It really doesn't matter where we put register 0, since it is |
| a fixed register anyhow. */ |
| reg_alloc_order[0] = 24; |
| reg_alloc_order[24] = 0; |
| } |
| } |
| |
| |
| /* The MIPS debug format wants all automatic variables and arguments |
| to be in terms of the virtual frame pointer (stack pointer before |
| any adjustment in the function), while the MIPS 3.0 linker wants |
| the frame pointer to be the stack pointer after the initial |
| adjustment. So, we do the adjustment here. The arg pointer (which |
| is eliminated) points to the virtual frame pointer, while the frame |
| pointer (which may be eliminated) points to the stack pointer after |
| the initial adjustments. */ |
| |
| HOST_WIDE_INT |
| mips_debugger_offset (rtx addr, HOST_WIDE_INT offset) |
| { |
| rtx offset2 = const0_rtx; |
| rtx reg = eliminate_constant_term (addr, &offset2); |
| |
| if (offset == 0) |
| offset = INTVAL (offset2); |
| |
| if (reg == stack_pointer_rtx || reg == frame_pointer_rtx |
| || reg == hard_frame_pointer_rtx) |
| { |
| HOST_WIDE_INT frame_size = (!cfun->machine->frame.initialized) |
| ? compute_frame_size (get_frame_size ()) |
| : cfun->machine->frame.total_size; |
| |
| /* MIPS16 frame is smaller */ |
| if (frame_pointer_needed && TARGET_MIPS16) |
| frame_size -= cfun->machine->frame.args_size; |
| |
| offset = offset - frame_size; |
| } |
| |
| /* sdbout_parms does not want this to crash for unrecognized cases. */ |
| #if 0 |
| else if (reg != arg_pointer_rtx) |
| fatal_insn ("mips_debugger_offset called with non stack/frame/arg pointer", |
| addr); |
| #endif |
| |
| return offset; |
| } |
| |
| /* Implement the PRINT_OPERAND macro. The MIPS-specific operand codes are: |
| |
| 'X' OP is CONST_INT, prints 32 bits in hexadecimal format = "0x%08x", |
| 'x' OP is CONST_INT, prints 16 bits in hexadecimal format = "0x%04x", |
| 'h' OP is HIGH, prints %hi(X), |
| 'd' output integer constant in decimal, |
| 'z' if the operand is 0, use $0 instead of normal operand. |
| 'D' print second part of double-word register or memory operand. |
| 'L' print low-order register of double-word register operand. |
| 'M' print high-order register of double-word register operand. |
| 'C' print part of opcode for a branch condition. |
| 'F' print part of opcode for a floating-point branch condition. |
| 'N' print part of opcode for a branch condition, inverted. |
| 'W' print part of opcode for a floating-point branch condition, inverted. |
| 'T' print 'f' for (eq:CC ...), 't' for (ne:CC ...), |
| 'z' for (eq:?I ...), 'n' for (ne:?I ...). |
| 't' like 'T', but with the EQ/NE cases reversed |
| 'Y' for a CONST_INT X, print mips_fp_conditions[X] |
| 'Z' print the operand and a comma for ISA_HAS_8CC, otherwise print nothing |
| 'R' print the reloc associated with LO_SUM |
| |
| The punctuation characters are: |
| |
| '(' Turn on .set noreorder |
| ')' Turn on .set reorder |
| '[' Turn on .set noat |
| ']' Turn on .set at |
| '<' Turn on .set nomacro |
| '>' Turn on .set macro |
| '{' Turn on .set volatile (not GAS) |
| '}' Turn on .set novolatile (not GAS) |
| '&' Turn on .set noreorder if filling delay slots |
| '*' Turn on both .set noreorder and .set nomacro if filling delay slots |
| '!' Turn on .set nomacro if filling delay slots |
| '#' Print nop if in a .set noreorder section. |
| '/' Like '#', but does nothing within a delayed branch sequence |
| '?' Print 'l' if we are to use a branch likely instead of normal branch. |
| '@' Print the name of the assembler temporary register (at or $1). |
| '.' Print the name of the register with a hard-wired zero (zero or $0). |
| '^' Print the name of the pic call-through register (t9 or $25). |
| '$' Print the name of the stack pointer register (sp or $29). |
| '+' Print the name of the gp register (usually gp or $28). |
| '~' Output a branch alignment to LABEL_ALIGN(NULL). */ |
| |
| void |
| print_operand (FILE *file, rtx op, int letter) |
| { |
| register enum rtx_code code; |
| |
| if (PRINT_OPERAND_PUNCT_VALID_P (letter)) |
| { |
| switch (letter) |
| { |
| case '?': |
| if (mips_branch_likely) |
| putc ('l', file); |
| break; |
| |
| case '@': |
| fputs (reg_names [GP_REG_FIRST + 1], file); |
| break; |
| |
| case '^': |
| fputs (reg_names [PIC_FUNCTION_ADDR_REGNUM], file); |
| break; |
| |
| case '.': |
| fputs (reg_names [GP_REG_FIRST + 0], file); |
| break; |
| |
| case '$': |
| fputs (reg_names[STACK_POINTER_REGNUM], file); |
| break; |
| |
| case '+': |
| fputs (reg_names[PIC_OFFSET_TABLE_REGNUM], file); |
| break; |
| |
| case '&': |
| if (final_sequence != 0 && set_noreorder++ == 0) |
| fputs (".set\tnoreorder\n\t", file); |
| break; |
| |
| case '*': |
| if (final_sequence != 0) |
| { |
| if (set_noreorder++ == 0) |
| fputs (".set\tnoreorder\n\t", file); |
| |
| if (set_nomacro++ == 0) |
| fputs (".set\tnomacro\n\t", file); |
| } |
| break; |
| |
| case '!': |
| if (final_sequence != 0 && set_nomacro++ == 0) |
| fputs ("\n\t.set\tnomacro", file); |
| break; |
| |
| case '#': |
| if (set_noreorder != 0) |
| fputs ("\n\tnop", file); |
| break; |
| |
| case '/': |
| /* Print an extra newline so that the delayed insn is separated |
| from the following ones. This looks neater and is consistent |
| with non-nop delayed sequences. */ |
| if (set_noreorder != 0 && final_sequence == 0) |
| fputs ("\n\tnop\n", file); |
| break; |
| |
| case '(': |
| if (set_noreorder++ == 0) |
| fputs (".set\tnoreorder\n\t", file); |
| break; |
| |
| case ')': |
| if (set_noreorder == 0) |
| error ("internal error: %%) found without a %%( in assembler pattern"); |
| |
| else if (--set_noreorder == 0) |
| fputs ("\n\t.set\treorder", file); |
| |
| break; |
| |
| case '[': |
| if (set_noat++ == 0) |
| fputs (".set\tnoat\n\t", file); |
| break; |
| |
| case ']': |
| if (set_noat == 0) |
| error ("internal error: %%] found without a %%[ in assembler pattern"); |
| else if (--set_noat == 0) |
| fputs ("\n\t.set\tat", file); |
| |
| break; |
| |
| case '<': |
| if (set_nomacro++ == 0) |
| fputs (".set\tnomacro\n\t", file); |
| break; |
| |
| case '>': |
| if (set_nomacro == 0) |
| error ("internal error: %%> found without a %%< in assembler pattern"); |
| else if (--set_nomacro == 0) |
| fputs ("\n\t.set\tmacro", file); |
| |
| break; |
| |
| case '{': |
| if (set_volatile++ == 0) |
| fputs ("#.set\tvolatile\n\t", file); |
| break; |
| |
| case '}': |
| if (set_volatile == 0) |
| error ("internal error: %%} found without a %%{ in assembler pattern"); |
| else if (--set_volatile == 0) |
| fputs ("\n\t#.set\tnovolatile", file); |
| |
| break; |
| |
| case '~': |
| { |
| if (align_labels_log > 0) |
| ASM_OUTPUT_ALIGN (file, align_labels_log); |
| } |
| break; |
| |
| default: |
| error ("PRINT_OPERAND: unknown punctuation '%c'", letter); |
| break; |
| } |
| |
| return; |
| } |
| |
| if (! op) |
| { |
| error ("PRINT_OPERAND null pointer"); |
| return; |
| } |
| |
| code = GET_CODE (op); |
| |
| if (letter == 'C') |
| switch (code) |
| { |
| case EQ: fputs ("eq", file); break; |
| case NE: fputs ("ne", file); break; |
| case GT: fputs ("gt", file); break; |
| case GE: fputs ("ge", file); break; |
| case LT: fputs ("lt", file); break; |
| case LE: fputs ("le", file); break; |
| case GTU: fputs ("gtu", file); break; |
| case GEU: fputs ("geu", file); break; |
| case LTU: fputs ("ltu", file); break; |
| case LEU: fputs ("leu", file); break; |
| default: |
| fatal_insn ("PRINT_OPERAND, invalid insn for %%C", op); |
| } |
| |
| else if (letter == 'N') |
| switch (code) |
| { |
| case EQ: fputs ("ne", file); break; |
| case NE: fputs ("eq", file); break; |
| case GT: fputs ("le", file); break; |
| case GE: fputs ("lt", file); break; |
| case LT: fputs ("ge", file); break; |
| case LE: fputs ("gt", file); break; |
| case GTU: fputs ("leu", file); break; |
| case GEU: fputs ("ltu", file); break; |
| case LTU: fputs ("geu", file); break; |
| case LEU: fputs ("gtu", file); break; |
| default: |
| fatal_insn ("PRINT_OPERAND, invalid insn for %%N", op); |
| } |
| |
| else if (letter == 'F') |
| switch (code) |
| { |
| case EQ: fputs ("c1f", file); break; |
| case NE: fputs ("c1t", file); break; |
| default: |
| fatal_insn ("PRINT_OPERAND, invalid insn for %%F", op); |
| } |
| |
| else if (letter == 'W') |
| switch (code) |
| { |
| case EQ: fputs ("c1t", file); break; |
| case NE: fputs ("c1f", file); break; |
| default: |
| fatal_insn ("PRINT_OPERAND, invalid insn for %%W", op); |
| } |
| |
| else if (letter == 'h') |
| { |
| if (GET_CODE (op) == HIGH) |
| op = XEXP (op, 0); |
| |
| print_operand_reloc (file, op, mips_hi_relocs); |
| } |
| |
| else if (letter == 'R') |
| print_operand_reloc (file, op, mips_lo_relocs); |
| |
| else if (letter == 'Y') |
| { |
| if (GET_CODE (op) == CONST_INT |
| && ((unsigned HOST_WIDE_INT) INTVAL (op) |
| < ARRAY_SIZE (mips_fp_conditions))) |
| fputs (mips_fp_conditions[INTVAL (op)], file); |
| else |
| output_operand_lossage ("invalid %%Y value"); |
| } |
| |
| else if (letter == 'Z') |
| { |
| if (ISA_HAS_8CC) |
| { |
| print_operand (file, op, 0); |
| fputc (',', file); |
| } |
| } |
| |
| else if (code == REG || code == SUBREG) |
| { |
| register int regnum; |
| |
| if (code == REG) |
| regnum = REGNO (op); |
| else |
| regnum = true_regnum (op); |
| |
| if ((letter == 'M' && ! WORDS_BIG_ENDIAN) |
| || (letter == 'L' && WORDS_BIG_ENDIAN) |
| || letter == 'D') |
| regnum++; |
| |
| fprintf (file, "%s", reg_names[regnum]); |
| } |
| |
| else if (code == MEM) |
| { |
| if (letter == 'D') |
| output_address (plus_constant (XEXP (op, 0), 4)); |
| else |
| output_address (XEXP (op, 0)); |
| } |
| |
| else if (letter == 'x' && GET_CODE (op) == CONST_INT) |
| fprintf (file, HOST_WIDE_INT_PRINT_HEX, 0xffff & INTVAL(op)); |
| |
| else if (letter == 'X' && GET_CODE(op) == CONST_INT) |
| fprintf (file, HOST_WIDE_INT_PRINT_HEX, INTVAL (op)); |
| |
| else if (letter == 'd' && GET_CODE(op) == CONST_INT) |
| fprintf (file, HOST_WIDE_INT_PRINT_DEC, (INTVAL(op))); |
| |
| else if (letter == 'z' && op == CONST0_RTX (GET_MODE (op))) |
| fputs (reg_names[GP_REG_FIRST], file); |
| |
| else if (letter == 'd' || letter == 'x' || letter == 'X') |
| output_operand_lossage ("invalid use of %%d, %%x, or %%X"); |
| |
| else if (letter == 'T' || letter == 't') |
| { |
| int truth = (code == NE) == (letter == 'T'); |
| fputc ("zfnt"[truth * 2 + (GET_MODE (op) == CCmode)], file); |
| } |
| |
| else if (CONST_GP_P (op)) |
| fputs (reg_names[GLOBAL_POINTER_REGNUM], file); |
| |
| else |
| output_addr_const (file, op); |
| } |
| |
| |
| /* Print symbolic operand OP, which is part of a HIGH or LO_SUM. |
| RELOCS is the array of relocations to use. */ |
| |
| static void |
| print_operand_reloc (FILE *file, rtx op, const char **relocs) |
| { |
| enum mips_symbol_type symbol_type; |
| const char *p; |
| rtx base; |
| HOST_WIDE_INT offset; |
| |
| if (!mips_symbolic_constant_p (op, &symbol_type) || relocs[symbol_type] == 0) |
| fatal_insn ("PRINT_OPERAND, invalid operand for relocation", op); |
| |
| /* If OP uses an UNSPEC address, we want to print the inner symbol. */ |
| mips_split_const (op, &base, &offset); |
| if (UNSPEC_ADDRESS_P (base)) |
| op = plus_constant (UNSPEC_ADDRESS (base), offset); |
| |
| fputs (relocs[symbol_type], file); |
| output_addr_const (file, op); |
| for (p = relocs[symbol_type]; *p != 0; p++) |
| if (*p == '(') |
| fputc (')', file); |
| } |
| |
| /* Output address operand X to FILE. */ |
| |
| void |
| print_operand_address (FILE *file, rtx x) |
| { |
| struct mips_address_info addr; |
| |
| if (mips_classify_address (&addr, x, word_mode, true)) |
| switch (addr.type) |
| { |
| case ADDRESS_REG: |
| print_operand (file, addr.offset, 0); |
| fprintf (file, "(%s)", reg_names[REGNO (addr.reg)]); |
| return; |
| |
| case ADDRESS_LO_SUM: |
| print_operand (file, addr.offset, 'R'); |
| fprintf (file, "(%s)", reg_names[REGNO (addr.reg)]); |
| return; |
| |
| case ADDRESS_CONST_INT: |
| output_addr_const (file, x); |
| fprintf (file, "(%s)", reg_names[0]); |
| return; |
| |
| case ADDRESS_SYMBOLIC: |
| output_addr_const (file, x); |
| return; |
| } |
| gcc_unreachable (); |
| } |
| |
| /* When using assembler macros, keep track of all of small-data externs |
| so that mips_file_end can emit the appropriate declarations for them. |
| |
| In most cases it would be safe (though pointless) to emit .externs |
| for other symbols too. One exception is when an object is within |
| the -G limit but declared by the user to be in a section other |
| than .sbss or .sdata. */ |
| |
| int |
| mips_output_external (FILE *file ATTRIBUTE_UNUSED, tree decl, const char *name) |
| { |
| register struct extern_list *p; |
| |
| if (!TARGET_EXPLICIT_RELOCS && mips_in_small_data_p (decl)) |
| { |
| p = (struct extern_list *) ggc_alloc (sizeof (struct extern_list)); |
| p->next = extern_head; |
| p->name = name; |
| p->size = int_size_in_bytes (TREE_TYPE (decl)); |
| extern_head = p; |
| } |
| |
| if (TARGET_IRIX && mips_abi == ABI_32 && TREE_CODE (decl) == FUNCTION_DECL) |
| { |
| p = (struct extern_list *) ggc_alloc (sizeof (struct extern_list)); |
| p->next = extern_head; |
| p->name = name; |
| p->size = -1; |
| extern_head = p; |
| } |
| |
| return 0; |
| } |
| |
| #if TARGET_IRIX |
| static void |
| irix_output_external_libcall (rtx fun) |
| { |
| register struct extern_list *p; |
| |
| if (mips_abi == ABI_32) |
| { |
| p = (struct extern_list *) ggc_alloc (sizeof (struct extern_list)); |
| p->next = extern_head; |
| p->name = XSTR (fun, 0); |
| p->size = -1; |
| extern_head = p; |
| } |
| } |
| #endif |
| |
| /* Emit a new filename to a stream. If we are smuggling stabs, try to |
| put out a MIPS ECOFF file and a stab. */ |
| |
| void |
| mips_output_filename (FILE *stream, const char *name) |
| { |
| |
| /* If we are emitting DWARF-2, let dwarf2out handle the ".file" |
| directives. */ |
| if (write_symbols == DWARF2_DEBUG) |
| return; |
| else if (mips_output_filename_first_time) |
| { |
| mips_output_filename_first_time = 0; |
| num_source_filenames += 1; |
| current_function_file = name; |
| fprintf (stream, "\t.file\t%d ", num_source_filenames); |
| output_quoted_string (stream, name); |
| putc ('\n', stream); |
| } |
| |
| /* If we are emitting stabs, let dbxout.c handle this (except for |
| the mips_output_filename_first_time case). */ |
| else if (write_symbols == DBX_DEBUG) |
| return; |
| |
| else if (name != current_function_file |
| && strcmp (name, current_function_file) != 0) |
| { |
| num_source_filenames += 1; |
| current_function_file = name; |
| fprintf (stream, "\t.file\t%d ", num_source_filenames); |
| output_quoted_string (stream, name); |
| putc ('\n', stream); |
| } |
| } |
| |
| /* Output an ASCII string, in a space-saving way. PREFIX is the string |
| that should be written before the opening quote, such as "\t.ascii\t" |
| for real string data or "\t# " for a comment. */ |
| |
| void |
| mips_output_ascii (FILE *stream, const char *string_param, size_t len, |
| const char *prefix) |
| { |
| size_t i; |
| int cur_pos = 17; |
| register const unsigned char *string = |
| (const unsigned char *)string_param; |
| |
| fprintf (stream, "%s\"", prefix); |
| for (i = 0; i < len; i++) |
| { |
| register int c = string[i]; |
| |
| if (ISPRINT (c)) |
| { |
| if (c == '\\' || c == '\"') |
| { |
| putc ('\\', stream); |
| cur_pos++; |
| } |
| putc (c, stream); |
| cur_pos++; |
| } |
| else |
| { |
| fprintf (stream, "\\%03o", c); |
| cur_pos += 4; |
| } |
| |
| if (cur_pos > 72 && i+1 < len) |
| { |
| cur_pos = 17; |
| fprintf (stream, "\"\n%s\"", prefix); |
| } |
| } |
| fprintf (stream, "\"\n"); |
| } |
| |
| /* Implement TARGET_ASM_FILE_START. */ |
| |
| static void |
| mips_file_start (void) |
| { |
| default_file_start (); |
| |
| if (!TARGET_IRIX) |
| { |
| /* Generate a special section to describe the ABI switches used to |
| produce the resultant binary. This used to be done by the assembler |
| setting bits in the ELF header's flags field, but we have run out of |
| bits. GDB needs this information in order to be able to correctly |
| debug these binaries. See the function mips_gdbarch_init() in |
| gdb/mips-tdep.c. This is unnecessary for the IRIX 5/6 ABIs and |
| causes unnecessary IRIX 6 ld warnings. */ |
| const char * abi_string = NULL; |
| |
| switch (mips_abi) |
| { |
| case ABI_32: abi_string = "abi32"; break; |
| case ABI_N32: abi_string = "abiN32"; break; |
| case ABI_64: abi_string = "abi64"; break; |
| case ABI_O64: abi_string = "abiO64"; break; |
| case ABI_EABI: abi_string = TARGET_64BIT ? "eabi64" : "eabi32"; break; |
| default: |
| gcc_unreachable (); |
| } |
| /* Note - we use fprintf directly rather than called named_section() |
| because in this way we can avoid creating an allocated section. We |
| do not want this section to take up any space in the running |
| executable. */ |
| fprintf (asm_out_file, "\t.section .mdebug.%s\n", abi_string); |
| |
| /* There is no ELF header flag to distinguish long32 forms of the |
| EABI from long64 forms. Emit a special section to help tools |
| such as GDB. */ |
| if (mips_abi == ABI_EABI) |
| fprintf (asm_out_file, "\t.section .gcc_compiled_long%d\n", |
| TARGET_LONG64 ? 64 : 32); |
| |
| /* Restore the default section. */ |
| fprintf (asm_out_file, "\t.previous\n"); |
| } |
| |
| /* Generate the pseudo ops that System V.4 wants. */ |
| if (TARGET_ABICALLS) |
| /* ??? but do not want this (or want pic0) if -non-shared? */ |
| fprintf (asm_out_file, "\t.abicalls\n"); |
| |
| if (TARGET_MIPS16) |
| fprintf (asm_out_file, "\t.set\tmips16\n"); |
| |
| if (flag_verbose_asm) |
| fprintf (asm_out_file, "\n%s -G value = %d, Arch = %s, ISA = %d\n", |
| ASM_COMMENT_START, |
| mips_section_threshold, mips_arch_info->name, mips_isa); |
| } |
| |
| #ifdef BSS_SECTION_ASM_OP |
| /* Implement ASM_OUTPUT_ALIGNED_BSS. This differs from the default only |
| in the use of sbss. */ |
| |
| void |
| mips_output_aligned_bss (FILE *stream, tree decl, const char *name, |
| unsigned HOST_WIDE_INT size, int align) |
| { |
| extern tree last_assemble_variable_decl; |
| |
| if (mips_in_small_data_p (decl)) |
| named_section (0, ".sbss", 0); |
| else |
| bss_section (); |
| ASM_OUTPUT_ALIGN (stream, floor_log2 (align / BITS_PER_UNIT)); |
| last_assemble_variable_decl = decl; |
| ASM_DECLARE_OBJECT_NAME (stream, name, decl); |
| ASM_OUTPUT_SKIP (stream, size != 0 ? size : 1); |
| } |
| #endif |
| |
| /* Implement TARGET_ASM_FILE_END. When using assembler macros, emit |
| .externs for any small-data variables that turned out to be external. */ |
| |
| static void |
| mips_file_end (void) |
| { |
| tree name_tree; |
| struct extern_list *p; |
| |
| if (extern_head) |
| { |
| fputs ("\n", asm_out_file); |
| |
| for (p = extern_head; p != 0; p = p->next) |
| { |
| name_tree = get_identifier (p->name); |
| |
| /* Positively ensure only one .extern for any given symbol. */ |
| if (!TREE_ASM_WRITTEN (name_tree) |
| && TREE_SYMBOL_REFERENCED (name_tree)) |
| { |
| TREE_ASM_WRITTEN (name_tree) = 1; |
| /* In IRIX 5 or IRIX 6 for the O32 ABI, we must output a |
| `.global name .text' directive for every used but |
| undefined function. If we don't, the linker may perform |
| an optimization (skipping over the insns that set $gp) |
| when it is unsafe. */ |
| if (TARGET_IRIX && mips_abi == ABI_32 && p->size == -1) |
| { |
| fputs ("\t.globl ", asm_out_file); |
| assemble_name (asm_out_file, p->name); |
| fputs (" .text\n", asm_out_file); |
| } |
| else |
| { |
| fputs ("\t.extern\t", asm_out_file); |
| assemble_name (asm_out_file, p->name); |
| fprintf (asm_out_file, ", %d\n", p->size); |
| } |
| } |
| } |
| } |
| } |
| |
| /* Implement ASM_OUTPUT_ALIGNED_DECL_COMMON. This is usually the same as the |
| elfos.h version, but we also need to handle -muninit-const-in-rodata. */ |
| |
| void |
| mips_output_aligned_decl_common (FILE *stream, tree decl, const char *name, |
| unsigned HOST_WIDE_INT size, |
| unsigned int align) |
| { |
| /* If the target wants uninitialized const declarations in |
| .rdata then don't put them in .comm. */ |
| if (TARGET_EMBEDDED_DATA && TARGET_UNINIT_CONST_IN_RODATA |
| && TREE_CODE (decl) == VAR_DECL && TREE_READONLY (decl) |
| && (DECL_INITIAL (decl) == 0 || DECL_INITIAL (decl) == error_mark_node)) |
| { |
| if (TREE_PUBLIC (decl) && DECL_NAME (decl)) |
| targetm.asm_out.globalize_label (stream, name); |
| |
| readonly_data_section (); |
| ASM_OUTPUT_ALIGN (stream, floor_log2 (align / BITS_PER_UNIT)); |
| mips_declare_object (stream, name, "", |
| ":\n\t.space\t" HOST_WIDE_INT_PRINT_UNSIGNED "\n", |
| size); |
| } |
| else |
| mips_declare_common_object (stream, name, "\n\t.comm\t", |
| size, align, true); |
| } |
| |
| /* Declare a common object of SIZE bytes using asm directive INIT_STRING. |
| NAME is the name of the object and ALIGN is the required alignment |
| in bytes. TAKES_ALIGNMENT_P is true if the directive takes a third |
| alignment argument. */ |
| |
| void |
| mips_declare_common_object (FILE *stream, const char *name, |
| const char *init_string, |
| unsigned HOST_WIDE_INT size, |
| unsigned int align, bool takes_alignment_p) |
| { |
| if (!takes_alignment_p) |
| { |
| size += (align / BITS_PER_UNIT) - 1; |
| size -= size % (align / BITS_PER_UNIT); |
| mips_declare_object (stream, name, init_string, |
| "," HOST_WIDE_INT_PRINT_UNSIGNED "\n", size); |
| } |
| else |
| mips_declare_object (stream, name, init_string, |
| "," HOST_WIDE_INT_PRINT_UNSIGNED ",%u\n", |
| size, align / BITS_PER_UNIT); |
| } |
| |
| /* Emit either a label, .comm, or .lcomm directive. When using assembler |
| macros, mark the symbol as written so that mips_file_end won't emit an |
| .extern for it. STREAM is the output file, NAME is the name of the |
| symbol, INIT_STRING is the string that should be written before the |
| symbol and FINAL_STRING is the string that should be written after it. |
| FINAL_STRING is a printf() format that consumes the remaining arguments. */ |
| |
| void |
| mips_declare_object (FILE *stream, const char *name, const char *init_string, |
| const char *final_string, ...) |
| { |
| va_list ap; |
| |
| fputs (init_string, stream); |
| assemble_name (stream, name); |
| va_start (ap, final_string); |
| vfprintf (stream, final_string, ap); |
| va_end (ap); |
| |
| if (!TARGET_EXPLICIT_RELOCS) |
| { |
| tree name_tree = get_identifier (name); |
| TREE_ASM_WRITTEN (name_tree) = 1; |
| } |
| } |
| |
| #ifdef ASM_OUTPUT_SIZE_DIRECTIVE |
| extern int size_directive_output; |
| |
| /* Implement ASM_DECLARE_OBJECT_NAME. This is like most of the standard ELF |
| definitions except that it uses mips_declare_object() to emit the label. */ |
| |
| void |
| mips_declare_object_name (FILE *stream, const char *name, |
| tree decl ATTRIBUTE_UNUSED) |
| { |
| #ifdef ASM_OUTPUT_TYPE_DIRECTIVE |
| ASM_OUTPUT_TYPE_DIRECTIVE (stream, name, "object"); |
| #endif |
| |
| size_directive_output = 0; |
| if (!flag_inhibit_size_directive && DECL_SIZE (decl)) |
| { |
| HOST_WIDE_INT size; |
| |
| size_directive_output = 1; |
| size = int_size_in_bytes (TREE_TYPE (decl)); |
| ASM_OUTPUT_SIZE_DIRECTIVE (stream, name, size); |
| } |
| |
| mips_declare_object (stream, name, "", ":\n", 0); |
| } |
| |
| /* Implement ASM_FINISH_DECLARE_OBJECT. This is generic ELF stuff. */ |
| |
| void |
| mips_finish_declare_object (FILE *stream, tree decl, int top_level, int at_end) |
| { |
| const char *name; |
| |
| name = XSTR (XEXP (DECL_RTL (decl), 0), 0); |
| if (!flag_inhibit_size_directive |
| && DECL_SIZE (decl) != 0 |
| && !at_end && top_level |
| && DECL_INITIAL (decl) == error_mark_node |
| && !size_directive_output) |
| { |
| HOST_WIDE_INT size; |
| |
| size_directive_output = 1; |
| size = int_size_in_bytes (TREE_TYPE (decl)); |
| ASM_OUTPUT_SIZE_DIRECTIVE (stream, name, size); |
| } |
| } |
| #endif |
| |
| /* Return true if X is a small data address that can be rewritten |
| as a LO_SUM. */ |
| |
| static bool |
| mips_rewrite_small_data_p (rtx x) |
| { |
| enum mips_symbol_type symbol_type; |
| |
| return (TARGET_EXPLICIT_RELOCS |
| && mips_symbolic_constant_p (x, &symbol_type) |
| && symbol_type == SYMBOL_SMALL_DATA); |
| } |
| |
| |
| /* A for_each_rtx callback for mips_small_data_pattern_p. */ |
| |
| static int |
| mips_small_data_pattern_1 (rtx *loc, void *data ATTRIBUTE_UNUSED) |
| { |
| if (GET_CODE (*loc) == LO_SUM) |
| return -1; |
| |
| return mips_rewrite_small_data_p (*loc); |
| } |
| |
| /* Return true if OP refers to small data symbols directly, not through |
| a LO_SUM. */ |
| |
| bool |
| mips_small_data_pattern_p (rtx op) |
| { |
| return for_each_rtx (&op, mips_small_data_pattern_1, 0); |
| } |
| |
| /* A for_each_rtx callback, used by mips_rewrite_small_data. */ |
| |
| static int |
| mips_rewrite_small_data_1 (rtx *loc, void *data ATTRIBUTE_UNUSED) |
| { |
| if (mips_rewrite_small_data_p (*loc)) |
| *loc = gen_rtx_LO_SUM (Pmode, pic_offset_table_rtx, *loc); |
| |
| if (GET_CODE (*loc) == LO_SUM) |
| return -1; |
| |
| return 0; |
| } |
| |
| /* If possible, rewrite OP so that it refers to small data using |
| explicit relocations. */ |
| |
| rtx |
| mips_rewrite_small_data (rtx op) |
| { |
| op = copy_insn (op); |
| for_each_rtx (&op, mips_rewrite_small_data_1, 0); |
| return op; |
| } |
| |
| /* Return true if the current function has an insn that implicitly |
| refers to $gp. */ |
| |
| static bool |
| mips_function_has_gp_insn (void) |
| { |
| /* Don't bother rechecking if we found one last time. */ |
| if (!cfun->machine->has_gp_insn_p) |
| { |
| rtx insn; |
| |
| push_topmost_sequence (); |
| for (insn = get_insns (); insn; insn = NEXT_INSN (insn)) |
| if (INSN_P (insn) |
| && GET_CODE (PATTERN (insn)) != USE |
| && GET_CODE (PATTERN (insn)) != CLOBBER |
| && (get_attr_got (insn) != GOT_UNSET |
| || small_data_pattern (PATTERN (insn), VOIDmode))) |
| break; |
| pop_topmost_sequence (); |
| |
| cfun->machine->has_gp_insn_p = (insn != 0); |
| } |
| return cfun->machine->has_gp_insn_p; |
| } |
| |
| |
| /* Return the register that should be used as the global pointer |
| within this function. Return 0 if the function doesn't need |
| a global pointer. */ |
| |
| static unsigned int |
| mips_global_pointer (void) |
| { |
| unsigned int regno; |
| |
| /* $gp is always available in non-abicalls code. */ |
| if (!TARGET_ABICALLS) |
| return GLOBAL_POINTER_REGNUM; |
| |
| /* We must always provide $gp when it is used implicitly. */ |
| if (!TARGET_EXPLICIT_RELOCS) |
| return GLOBAL_POINTER_REGNUM; |
| |
| /* FUNCTION_PROFILER includes a jal macro, so we need to give it |
| a valid gp. */ |
| if (current_function_profile) |
| return GLOBAL_POINTER_REGNUM; |
| |
| /* If the function has a nonlocal goto, $gp must hold the correct |
| global pointer for the target function. */ |
| if (current_function_has_nonlocal_goto) |
| return GLOBAL_POINTER_REGNUM; |
| |
| /* If the gp is never referenced, there's no need to initialize it. |
| Note that reload can sometimes introduce constant pool references |
| into a function that otherwise didn't need them. For example, |
| suppose we have an instruction like: |
| |
| (set (reg:DF R1) (float:DF (reg:SI R2))) |
| |
| If R2 turns out to be constant such as 1, the instruction may have a |
| REG_EQUAL note saying that R1 == 1.0. Reload then has the option of |
| using this constant if R2 doesn't get allocated to a register. |
| |
| In cases like these, reload will have added the constant to the pool |
| but no instruction will yet refer to it. */ |
| if (!regs_ever_live[GLOBAL_POINTER_REGNUM] |
| && !current_function_uses_const_pool |
| && !mips_function_has_gp_insn ()) |
| return 0; |
| |
| /* We need a global pointer, but perhaps we can use a call-clobbered |
| register instead of $gp. */ |
| if (TARGET_NEWABI && current_function_is_leaf) |
| for (regno = GP_REG_FIRST; regno <= GP_REG_LAST; regno++) |
| if (!regs_ever_live[regno] |
| && call_used_regs[regno] |
| && !fixed_regs[regno] |
| && regno != PIC_FUNCTION_ADDR_REGNUM) |
| return regno; |
| |
| return GLOBAL_POINTER_REGNUM; |
| } |
| |
| |
| /* Return true if the current function must save REGNO. */ |
| |
| static bool |
| mips_save_reg_p (unsigned int regno) |
| { |
| /* We only need to save $gp for NewABI PIC. */ |
| if (regno == GLOBAL_POINTER_REGNUM) |
| return (TARGET_ABICALLS && TARGET_NEWABI |
| && cfun->machine->global_pointer == regno); |
| |
| /* Check call-saved registers. */ |
| if (regs_ever_live[regno] && !call_used_regs[regno]) |
| return true; |
| |
| /* We need to save the old frame pointer before setting up a new one. */ |
| if (regno == HARD_FRAME_POINTER_REGNUM && frame_pointer_needed) |
| return true; |
| |
| /* We need to save the incoming return address if it is ever clobbered |
| within the function. */ |
| if (regno == GP_REG_FIRST + 31 && regs_ever_live[regno]) |
| return true; |
| |
| if (TARGET_MIPS16) |
| { |
| tree return_type; |
| |
| return_type = DECL_RESULT (current_function_decl); |
| |
| /* $18 is a special case in mips16 code. It may be used to call |
| a function which returns a floating point value, but it is |
| marked in call_used_regs. */ |
| if (regno == GP_REG_FIRST + 18 && regs_ever_live[regno]) |
| return true; |
| |
| /* $31 is also a special case. It will be used to copy a return |
| value into the floating point registers if the return value is |
| floating point. */ |
| if (regno == GP_REG_FIRST + 31 |
| && mips16_hard_float |
| && !aggregate_value_p (return_type, current_function_decl) |
| && GET_MODE_CLASS (DECL_MODE (return_type)) == MODE_FLOAT |
| && GET_MODE_SIZE (DECL_MODE (return_type)) <= UNITS_PER_FPVALUE) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| |
| /* Return the bytes needed to compute the frame pointer from the current |
| stack pointer. SIZE is the size (in bytes) of the local variables. |
| |
| Mips stack frames look like: |
| |
| Before call After call |
| +-----------------------+ +-----------------------+ |
| high | | | | |
| mem. | | | | |
| | caller's temps. | | caller's temps. | |
| | | | | |
| +-----------------------+ +-----------------------+ |
| | | | | |
| | arguments on stack. | | arguments on stack. | |
| | | | | |
| +-----------------------+ +-----------------------+ |
| | 4 words to save | | 4 words to save | |
| | arguments passed | | arguments passed | |
| | in registers, even | | in registers, even | |
| SP->| if not passed. | VFP->| if not passed. | |
| +-----------------------+ +-----------------------+ |
| | | |
| | fp register save | |
| | | |
| +-----------------------+ |
| | | |
| | gp register save | |
| | | |
| +-----------------------+ |
| | | |
| | local variables | |
| | | |
| +-----------------------+ |
| | | |
| | alloca allocations | |
| | | |
| +-----------------------+ |
| | | |
| | GP save for V.4 abi | |
| | | |
| +-----------------------+ |
| | | |
| | arguments on stack | |
| | | |
| +-----------------------+ |
| | 4 words to save | |
| | arguments passed | |
| | in registers, even | |
| low SP->| if not passed. | |
| memory +-----------------------+ |
| |
| */ |
| |
| HOST_WIDE_INT |
| compute_frame_size (HOST_WIDE_INT size) |
| { |
| unsigned int regno; |
| HOST_WIDE_INT total_size; /* # bytes that the entire frame takes up */ |
| HOST_WIDE_INT var_size; /* # bytes that variables take up */ |
| HOST_WIDE_INT args_size; /* # bytes that outgoing arguments take up */ |
| HOST_WIDE_INT cprestore_size; /* # bytes that the cprestore slot takes up */ |
| HOST_WIDE_INT gp_reg_rounded; /* # bytes needed to store gp after rounding */ |
| HOST_WIDE_INT gp_reg_size; /* # bytes needed to store gp regs */ |
| HOST_WIDE_INT fp_reg_size; /* # bytes needed to store fp regs */ |
| unsigned int mask; /* mask of saved gp registers */ |
| unsigned int fmask; /* mask of saved fp registers */ |
| |
| cfun->machine->global_pointer = mips_global_pointer (); |
| |
| gp_reg_size = 0; |
| fp_reg_size = 0; |
| mask = 0; |
| fmask = 0; |
| var_size = MIPS_STACK_ALIGN (size); |
| args_size = current_function_outgoing_args_size; |
| cprestore_size = MIPS_STACK_ALIGN (STARTING_FRAME_OFFSET) - args_size; |
| |
| /* The space set aside by STARTING_FRAME_OFFSET isn't needed in leaf |
| functions. If the function has local variables, we're committed |
| to allocating it anyway. Otherwise reclaim it here. */ |
| if (var_size == 0 && current_function_is_leaf) |
| cprestore_size = args_size = 0; |
| |
| /* The MIPS 3.0 linker does not like functions that dynamically |
| allocate the stack and have 0 for STACK_DYNAMIC_OFFSET, since it |
| looks like we are trying to create a second frame pointer to the |
| function, so allocate some stack space to make it happy. */ |
| |
| if (args_size == 0 && current_function_calls_alloca) |
| args_size = 4 * UNITS_PER_WORD; |
| |
| total_size = var_size + args_size + cprestore_size; |
| |
| /* Calculate space needed for gp registers. */ |
| for (regno = GP_REG_FIRST; regno <= GP_REG_LAST; regno++) |
| if (mips_save_reg_p (regno)) |
| { |
| gp_reg_size += GET_MODE_SIZE (gpr_mode); |
| mask |= 1 << (regno - GP_REG_FIRST); |
| } |
| |
| /* We need to restore these for the handler. */ |
| if (current_function_calls_eh_return) |
| { |
| unsigned int i; |
| for (i = 0; ; ++i) |
| { |
| regno = EH_RETURN_DATA_REGNO (i); |
| if (regno == INVALID_REGNUM) |
| break; |
| gp_reg_size += GET_MODE_SIZE (gpr_mode); |
| mask |= 1 << (regno - GP_REG_FIRST); |
| } |
| } |
| |
| /* This loop must iterate over the same space as its companion in |
| save_restore_insns. */ |
| for (regno = (FP_REG_LAST - FP_INC + 1); |
| regno >= FP_REG_FIRST; |
| regno -= FP_INC) |
| { |
| if (mips_save_reg_p (regno)) |
| { |
| fp_reg_size += FP_INC * UNITS_PER_FPREG; |
| fmask |= ((1 << FP_INC) - 1) << (regno - FP_REG_FIRST); |
| } |
| } |
| |
| gp_reg_rounded = MIPS_STACK_ALIGN (gp_reg_size); |
| total_size += gp_reg_rounded + MIPS_STACK_ALIGN (fp_reg_size); |
| |
| /* Add in space reserved on the stack by the callee for storing arguments |
| passed in registers. */ |
| if (!TARGET_OLDABI) |
| total_size += MIPS_STACK_ALIGN (current_function_pretend_args_size); |
| |
| /* Save other computed information. */ |
| cfun->machine->frame.total_size = total_size; |
| cfun->machine->frame.var_size = var_size; |
| cfun->machine->frame.args_size = args_size; |
| cfun->machine->frame.cprestore_size = cprestore_size; |
| cfun->machine->frame.gp_reg_size = gp_reg_size; |
| cfun->machine->frame.fp_reg_size = fp_reg_size; |
| cfun->machine->frame.mask = mask; |
| cfun->machine->frame.fmask = fmask; |
| cfun->machine->frame.initialized = reload_completed; |
| cfun->machine->frame.num_gp = gp_reg_size / UNITS_PER_WORD; |
| cfun->machine->frame.num_fp = fp_reg_size / (FP_INC * UNITS_PER_FPREG); |
| |
| if (mask) |
| { |
| HOST_WIDE_INT offset; |
| |
| offset = (args_size + cprestore_size + var_size |
| + gp_reg_size - GET_MODE_SIZE (gpr_mode)); |
| cfun->machine->frame.gp_sp_offset = offset; |
| cfun->machine->frame.gp_save_offset = offset - total_size; |
| } |
| else |
| { |
| cfun->machine->frame.gp_sp_offset = 0; |
| cfun->machine->frame.gp_save_offset = 0; |
| } |
| |
| if (fmask) |
| { |
| HOST_WIDE_INT offset; |
| |
| offset = (args_size + cprestore_size + var_size |
| + gp_reg_rounded + fp_reg_size |
| - FP_INC * UNITS_PER_FPREG); |
| cfun->machine->frame.fp_sp_offset = offset; |
| cfun->machine->frame.fp_save_offset = offset - total_size; |
| } |
| else |
| { |
| cfun->machine->frame.fp_sp_offset = 0; |
| cfun->machine->frame.fp_save_offset = 0; |
| } |
| |
| /* Ok, we're done. */ |
| return total_size; |
| } |
| |
| /* Implement INITIAL_ELIMINATION_OFFSET. FROM is either the frame |
| pointer or argument pointer. TO is either the stack pointer or |
| hard frame pointer. */ |
| |
| HOST_WIDE_INT |
| mips_initial_elimination_offset (int from, int to) |
| { |
| HOST_WIDE_INT offset; |
| |
| compute_frame_size (get_frame_size ()); |
| |
| /* Set OFFSET to the offset from the stack pointer. */ |
| switch (from) |
| { |
| case FRAME_POINTER_REGNUM: |
| offset = 0; |
| break; |
| |
| case ARG_POINTER_REGNUM: |
| offset = cfun->machine->frame.total_size; |
| if (TARGET_NEWABI) |
| offset -= current_function_pretend_args_size; |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| if (TARGET_MIPS16 && to == HARD_FRAME_POINTER_REGNUM) |
| offset -= cfun->machine->frame.args_size; |
| |
| return offset; |
| } |
| |
| /* Implement RETURN_ADDR_RTX. Note, we do not support moving |
| back to a previous frame. */ |
| rtx |
| mips_return_addr (int count, rtx frame ATTRIBUTE_UNUSED) |
| { |
| if (count != 0) |
| return const0_rtx; |
| |
| return get_hard_reg_initial_val (Pmode, GP_REG_FIRST + 31); |
| } |
| |
| /* Use FN to save or restore register REGNO. MODE is the register's |
| mode and OFFSET is the offset of its save slot from the current |
| stack pointer. */ |
| |
| static void |
| mips_save_restore_reg (enum machine_mode mode, int regno, |
| HOST_WIDE_INT offset, mips_save_restore_fn fn) |
| { |
| rtx mem; |
| |
| mem = gen_rtx_MEM (mode, plus_constant (stack_pointer_rtx, offset)); |
| |
| fn (gen_rtx_REG (mode, regno), mem); |
| } |
| |
| |
| /* Call FN for each register that is saved by the current function. |
| SP_OFFSET is the offset of the current stack pointer from the start |
| of the frame. */ |
| |
| static void |
| mips_for_each_saved_reg (HOST_WIDE_INT sp_offset, mips_save_restore_fn fn) |
| { |
| #define BITSET_P(VALUE, BIT) (((VALUE) & (1L << (BIT))) != 0) |
| |
| enum machine_mode fpr_mode; |
| HOST_WIDE_INT offset; |
| int regno; |
| |
| /* Save registers starting from high to low. The debuggers prefer at least |
| the return register be stored at func+4, and also it allows us not to |
| need a nop in the epilog if at least one register is reloaded in |
| addition to return address. */ |
| offset = cfun->machine->frame.gp_sp_offset - sp_offset; |
| for (regno = GP_REG_LAST; regno >= GP_REG_FIRST; regno--) |
| if (BITSET_P (cfun->machine->frame.mask, regno - GP_REG_FIRST)) |
| { |
| mips_save_restore_reg (gpr_mode, regno, offset, fn); |
| offset -= GET_MODE_SIZE (gpr_mode); |
| } |
| |
| /* This loop must iterate over the same space as its companion in |
| compute_frame_size. */ |
| offset = cfun->machine->frame.fp_sp_offset - sp_offset; |
| fpr_mode = (TARGET_SINGLE_FLOAT ? SFmode : DFmode); |
| for (regno = (FP_REG_LAST - FP_INC + 1); |
| regno >= FP_REG_FIRST; |
| regno -= FP_INC) |
| if (BITSET_P (cfun->machine->frame.fmask, regno - FP_REG_FIRST)) |
| { |
| mips_save_restore_reg (fpr_mode, regno, offset, fn); |
| offset -= GET_MODE_SIZE (fpr_mode); |
| } |
| #undef BITSET_P |
| } |
| |
| /* If we're generating n32 or n64 abicalls, and the current function |
| does not use $28 as its global pointer, emit a cplocal directive. |
| Use pic_offset_table_rtx as the argument to the directive. */ |
| |
| static void |
| mips_output_cplocal (void) |
| { |
| if (!TARGET_EXPLICIT_RELOCS |
| && cfun->machine->global_pointer > 0 |
| && cfun->machine->global_pointer != GLOBAL_POINTER_REGNUM) |
| output_asm_insn (".cplocal %+", 0); |
| } |
| |
| /* If we're generating n32 or n64 abicalls, emit instructions |
| to set up the global pointer. */ |
| |
| static void |
| mips_emit_loadgp (void) |
| { |
| if (TARGET_ABICALLS && TARGET_NEWABI && cfun->machine->global_pointer > 0) |
| { |
| rtx addr, offset, incoming_address; |
| |
| addr = XEXP (DECL_RTL (current_function_decl), 0); |
| offset = mips_unspec_address (addr, SYMBOL_GOTOFF_LOADGP); |
| incoming_address = gen_rtx_REG (Pmode, PIC_FUNCTION_ADDR_REGNUM); |
| emit_insn (gen_loadgp (offset, incoming_address)); |
| if (!TARGET_EXPLICIT_RELOCS) |
| emit_insn (gen_loadgp_blockage ()); |
| } |
| } |
| |
| /* Set up the stack and frame (if desired) for the function. */ |
| |
| static void |
| mips_output_function_prologue (FILE *file, HOST_WIDE_INT size ATTRIBUTE_UNUSED) |
| { |
| const char *fnname; |
| HOST_WIDE_INT tsize = cfun->machine->frame.total_size; |
| |
| #ifdef SDB_DEBUGGING_INFO |
| if (debug_info_level != DINFO_LEVEL_TERSE && write_symbols == SDB_DEBUG) |
| SDB_OUTPUT_SOURCE_LINE (file, DECL_SOURCE_LINE (current_function_decl)); |
| #endif |
| |
| /* In mips16 mode, we may need to generate a 32 bit to handle |
| floating point arguments. The linker will arrange for any 32 bit |
| functions to call this stub, which will then jump to the 16 bit |
| function proper. */ |
| if (TARGET_MIPS16 && !TARGET_SOFT_FLOAT |
| && current_function_args_info.fp_code != 0) |
| build_mips16_function_stub (file); |
| |
| if (!FUNCTION_NAME_ALREADY_DECLARED) |
| { |
| /* Get the function name the same way that toplev.c does before calling |
| assemble_start_function. This is needed so that the name used here |
| exactly matches the name used in ASM_DECLARE_FUNCTION_NAME. */ |
| fnname = XSTR (XEXP (DECL_RTL (current_function_decl), 0), 0); |
| |
| if (!flag_inhibit_size_directive) |
| { |
| fputs ("\t.ent\t", file); |
| assemble_name (file, fnname); |
| fputs ("\n", file); |
| } |
| |
| assemble_name (file, fnname); |
| fputs (":\n", file); |
| } |
| |
| /* Stop mips_file_end from treating this function as external. */ |
| if (TARGET_IRIX && mips_abi == ABI_32) |
| TREE_ASM_WRITTEN (DECL_NAME (cfun->decl)) = 1; |
| |
| if (!flag_inhibit_size_directive) |
| { |
| /* .frame FRAMEREG, FRAMESIZE, RETREG */ |
| fprintf (file, |
| "\t.frame\t%s," HOST_WIDE_INT_PRINT_DEC ",%s\t\t" |
| "# vars= " HOST_WIDE_INT_PRINT_DEC ", regs= %d/%d" |
| ", args= " HOST_WIDE_INT_PRINT_DEC |
| ", gp= " HOST_WIDE_INT_PRINT_DEC "\n", |
| (reg_names[(frame_pointer_needed) |
| ? HARD_FRAME_POINTER_REGNUM : STACK_POINTER_REGNUM]), |
| ((frame_pointer_needed && TARGET_MIPS16) |
| ? tsize - cfun->machine->frame.args_size |
| : tsize), |
| reg_names[GP_REG_FIRST + 31], |
| cfun->machine->frame.var_size, |
| cfun->machine->frame.num_gp, |
| cfun->machine->frame.num_fp, |
| cfun->machine->frame.args_size, |
| cfun->machine->frame.cprestore_size); |
| |
| /* .mask MASK, GPOFFSET; .fmask FPOFFSET */ |
| fprintf (file, "\t.mask\t0x%08x," HOST_WIDE_INT_PRINT_DEC "\n", |
| cfun->machine->frame.mask, |
| cfun->machine->frame.gp_save_offset); |
| fprintf (file, "\t.fmask\t0x%08x," HOST_WIDE_INT_PRINT_DEC "\n", |
| cfun->machine->frame.fmask, |
| cfun->machine->frame.fp_save_offset); |
| |
| /* Require: |
| OLD_SP == *FRAMEREG + FRAMESIZE => can find old_sp from nominated FP reg. |
| HIGHEST_GP_SAVED == *FRAMEREG + FRAMESIZE + GPOFFSET => can find saved regs. */ |
| } |
| |
| if (TARGET_ABICALLS && !TARGET_NEWABI && cfun->machine->global_pointer > 0) |
| { |
| /* Handle the initialization of $gp for SVR4 PIC. */ |
| if (!cfun->machine->all_noreorder_p) |
| output_asm_insn ("%(.cpload\t%^%)", 0); |
| else |
| output_asm_insn ("%(.cpload\t%^\n\t%<", 0); |
| } |
| else if (cfun->machine->all_noreorder_p) |
| output_asm_insn ("%(%<", 0); |
| |
| /* Tell the assembler which register we're using as the global |
| pointer. This is needed for thunks, since they can use either |
| explicit relocs or assembler macros. */ |
| mips_output_cplocal (); |
| } |
| |
| /* Make the last instruction frame related and note that it performs |
| the operation described by FRAME_PATTERN. */ |
| |
| static void |
| mips_set_frame_expr (rtx frame_pattern) |
| { |
| rtx insn; |
| |
| insn = get_last_insn (); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| REG_NOTES (insn) = alloc_EXPR_LIST (REG_FRAME_RELATED_EXPR, |
| frame_pattern, |
| REG_NOTES (insn)); |
| } |
| |
| |
| /* Return a frame-related rtx that stores REG at MEM. |
| REG must be a single register. */ |
| |
| static rtx |
| mips_frame_set (rtx mem, rtx reg) |
| { |
| rtx set; |
| |
| /* If we're saving the return address register and the dwarf return |
| address column differs from the hard register number, adjust the |
| note reg to refer to the former. */ |
| if (REGNO (reg) == GP_REG_FIRST + 31 |
| && DWARF_FRAME_RETURN_COLUMN != GP_REG_FIRST + 31) |
| reg = gen_rtx_REG (GET_MODE (reg), DWARF_FRAME_RETURN_COLUMN); |
| |
| set = gen_rtx_SET (VOIDmode, mem, reg); |
| RTX_FRAME_RELATED_P (set) = 1; |
| |
| return set; |
| } |
| |
| |
| /* Save register REG to MEM. Make the instruction frame-related. */ |
| |
| static void |
| mips_save_reg (rtx reg, rtx mem) |
| { |
| if (GET_MODE (reg) == DFmode && !TARGET_FLOAT64) |
| { |
| rtx x1, x2; |
| |
| if (mips_split_64bit_move_p (mem, reg)) |
| mips_split_64bit_move (mem, reg); |
| else |
| emit_move_insn (mem, reg); |
| |
| x1 = mips_frame_set (mips_subword (mem, 0), mips_subword (reg, 0)); |
| x2 = mips_frame_set (mips_subword (mem, 1), mips_subword (reg, 1)); |
| mips_set_frame_expr (gen_rtx_PARALLEL (VOIDmode, gen_rtvec (2, x1, x2))); |
| } |
| else |
| { |
| if (TARGET_MIPS16 |
| && REGNO (reg) != GP_REG_FIRST + 31 |
| && !M16_REG_P (REGNO (reg))) |
| { |
| /* Save a non-mips16 register by moving it through a temporary. |
| We don't need to do this for $31 since there's a special |
| instruction for it. */ |
| emit_move_insn (MIPS_PROLOGUE_TEMP (GET_MODE (reg)), reg); |
| emit_move_insn (mem, MIPS_PROLOGUE_TEMP (GET_MODE (reg))); |
| } |
| else |
| emit_move_insn (mem, reg); |
| |
| mips_set_frame_expr (mips_frame_set (mem, reg)); |
| } |
| } |
| |
| |
| /* Expand the prologue into a bunch of separate insns. */ |
| |
| void |
| mips_expand_prologue (void) |
| { |
| HOST_WIDE_INT size; |
| |
| if (cfun->machine->global_pointer > 0) |
| REGNO (pic_offset_table_rtx) = cfun->machine->global_pointer; |
| |
| size = compute_frame_size (get_frame_size ()); |
| |
| /* Save the registers. Allocate up to MIPS_MAX_FIRST_STACK_STEP |
| bytes beforehand; this is enough to cover the register save area |
| without going out of range. */ |
| if ((cfun->machine->frame.mask | cfun->machine->frame.fmask) != 0) |
| { |
| HOST_WIDE_INT step1; |
| |
| step1 = MIN (size, MIPS_MAX_FIRST_STACK_STEP); |
| RTX_FRAME_RELATED_P (emit_insn (gen_add3_insn (stack_pointer_rtx, |
| stack_pointer_rtx, |
| GEN_INT (-step1)))) = 1; |
| size -= step1; |
| mips_for_each_saved_reg (size, mips_save_reg); |
| } |
| |
| /* Allocate the rest of the frame. */ |
| if (size > 0) |
| { |
| if (SMALL_OPERAND (-size)) |
| RTX_FRAME_RELATED_P (emit_insn (gen_add3_insn (stack_pointer_rtx, |
| stack_pointer_rtx, |
| GEN_INT (-size)))) = 1; |
| else |
| { |
| emit_move_insn (MIPS_PROLOGUE_TEMP (Pmode), GEN_INT (size)); |
| if (TARGET_MIPS16) |
| { |
| /* There are no instructions to add or subtract registers |
| from the stack pointer, so use the frame pointer as a |
| temporary. We should always be using a frame pointer |
| in this case anyway. */ |
| gcc_assert (frame_pointer_needed); |
| emit_move_insn (hard_frame_pointer_rtx, stack_pointer_rtx); |
| emit_insn (gen_sub3_insn (hard_frame_pointer_rtx, |
| hard_frame_pointer_rtx, |
| MIPS_PROLOGUE_TEMP (Pmode))); |
| emit_move_insn (stack_pointer_rtx, hard_frame_pointer_rtx); |
| } |
| else |
| emit_insn (gen_sub3_insn (stack_pointer_rtx, |
| stack_pointer_rtx, |
| MIPS_PROLOGUE_TEMP (Pmode))); |
| |
| /* Describe the combined effect of the previous instructions. */ |
| mips_set_frame_expr |
| (gen_rtx_SET (VOIDmode, stack_pointer_rtx, |
| plus_constant (stack_pointer_rtx, -size))); |
| } |
| } |
| |
| /* Set up the frame pointer, if we're using one. In mips16 code, |
| we point the frame pointer ahead of the outgoing argument area. |
| This should allow more variables & incoming arguments to be |
| accessed with unextended instructions. */ |
| if (frame_pointer_needed) |
| { |
| if (TARGET_MIPS16 && cfun->machine->frame.args_size != 0) |
| { |
| rtx offset = GEN_INT (cfun->machine->frame.args_size); |
| RTX_FRAME_RELATED_P |
| (emit_insn (gen_add3_insn (hard_frame_pointer_rtx, |
| stack_pointer_rtx, |
| offset))) = 1; |
| } |
| else |
| RTX_FRAME_RELATED_P (emit_move_insn (hard_frame_pointer_rtx, |
| stack_pointer_rtx)) = 1; |
| } |
| |
| /* If generating o32/o64 abicalls, save $gp on the stack. */ |
| if (TARGET_ABICALLS && !TARGET_NEWABI && !current_function_is_leaf) |
| emit_insn (gen_cprestore (GEN_INT (current_function_outgoing_args_size))); |
| |
| mips_emit_loadgp (); |
| |
| /* If we are profiling, make sure no instructions are scheduled before |
| the call to mcount. */ |
| |
| if (current_function_profile) |
| emit_insn (gen_blockage ()); |
| } |
| |
| /* Do any necessary cleanup after a function to restore stack, frame, |
| and regs. */ |
| |
| #define RA_MASK BITMASK_HIGH /* 1 << 31 */ |
| |
| static void |
| mips_output_function_epilogue (FILE *file ATTRIBUTE_UNUSED, |
| HOST_WIDE_INT size ATTRIBUTE_UNUSED) |
| { |
| /* Reinstate the normal $gp. */ |
| REGNO (pic_offset_table_rtx) = GLOBAL_POINTER_REGNUM; |
| mips_output_cplocal (); |
| |
| if (cfun->machine->all_noreorder_p) |
| { |
| /* Avoid using %>%) since it adds excess whitespace. */ |
| output_asm_insn (".set\tmacro", 0); |
| output_asm_insn (".set\treorder", 0); |
| set_noreorder = set_nomacro = 0; |
| } |
| |
| if (!FUNCTION_NAME_ALREADY_DECLARED && !flag_inhibit_size_directive) |
| { |
| const char *fnname; |
| |
| /* Get the function name the same way that toplev.c does before calling |
| assemble_start_function. This is needed so that the name used here |
| exactly matches the name used in ASM_DECLARE_FUNCTION_NAME. */ |
| fnname = XSTR (XEXP (DECL_RTL (current_function_decl), 0), 0); |
| fputs ("\t.end\t", file); |
| assemble_name (file, fnname); |
| fputs ("\n", file); |
| } |
| } |
| |
| /* Emit instructions to restore register REG from slot MEM. */ |
| |
| static void |
| mips_restore_reg (rtx reg, rtx mem) |
| { |
| /* There's no mips16 instruction to load $31 directly. Load into |
| $7 instead and adjust the return insn appropriately. */ |
| if (TARGET_MIPS16 && REGNO (reg) == GP_REG_FIRST + 31) |
| reg = gen_rtx_REG (GET_MODE (reg), 7); |
| |
| if (TARGET_MIPS16 && !M16_REG_P (REGNO (reg))) |
| { |
| /* Can't restore directly; move through a temporary. */ |
| emit_move_insn (MIPS_EPILOGUE_TEMP (GET_MODE (reg)), mem); |
| emit_move_insn (reg, MIPS_EPILOGUE_TEMP (GET_MODE (reg))); |
| } |
| else |
| emit_move_insn (reg, mem); |
| } |
| |
| |
| /* Expand the epilogue into a bunch of separate insns. SIBCALL_P is true |
| if this epilogue precedes a sibling call, false if it is for a normal |
| "epilogue" pattern. */ |
| |
| void |
| mips_expand_epilogue (int sibcall_p) |
| { |
| HOST_WIDE_INT step1, step2; |
| rtx base, target; |
| |
| if (!sibcall_p && mips_can_use_return_insn ()) |
| { |
| emit_jump_insn (gen_return ()); |
| return; |
| } |
| |
| /* Split the frame into two. STEP1 is the amount of stack we should |
| deallocate before restoring the registers. STEP2 is the amount we |
| should deallocate afterwards. |
| |
| Start off by assuming that no registers need to be restored. */ |
| step1 = cfun->machine->frame.total_size; |
| step2 = 0; |
| |
| /* Work out which register holds the frame address. Account for the |
| frame pointer offset used by mips16 code. */ |
| if (!frame_pointer_needed) |
| base = stack_pointer_rtx; |
| else |
| { |
| base = hard_frame_pointer_rtx; |
| if (TARGET_MIPS16) |
| step1 -= cfun->machine->frame.args_size; |
| } |
| |
| /* If we need to restore registers, deallocate as much stack as |
| possible in the second step without going out of range. */ |
| if ((cfun->machine->frame.mask | cfun->machine->frame.fmask) != 0) |
| { |
| step2 = MIN (step1, MIPS_MAX_FIRST_STACK_STEP); |
| step1 -= step2; |
| } |
| |
| /* Set TARGET to BASE + STEP1. */ |
| target = base; |
| if (step1 > 0) |
| { |
| rtx adjust; |
| |
| /* Get an rtx for STEP1 that we can add to BASE. */ |
| adjust = GEN_INT (step1); |
| if (!SMALL_OPERAND (step1)) |
| { |
| emit_move_insn (MIPS_EPILOGUE_TEMP (Pmode), adjust); |
| adjust = MIPS_EPILOGUE_TEMP (Pmode); |
| } |
| |
| /* Normal mode code can copy the result straight into $sp. */ |
| if (!TARGET_MIPS16) |
| target = stack_pointer_rtx; |
| |
| emit_insn (gen_add3_insn (target, base, adjust)); |
| } |
| |
| /* Copy TARGET into the stack pointer. */ |
| if (target != stack_pointer_rtx) |
| emit_move_insn (stack_pointer_rtx, target); |
| |
| /* If we're using addressing macros for n32/n64 abicalls, $gp is |
| implicitly used by all SYMBOL_REFs. We must emit a blockage |
| insn before restoring it. */ |
| if (TARGET_ABICALLS && TARGET_NEWABI && !TARGET_EXPLICIT_RELOCS) |
| emit_insn (gen_blockage ()); |
| |
| /* Restore the registers. */ |
| mips_for_each_saved_reg (cfun->machine->frame.total_size - step2, |
| mips_restore_reg); |
| |
| /* Deallocate the final bit of the frame. */ |
| if (step2 > 0) |
| emit_insn (gen_add3_insn (stack_pointer_rtx, |
| stack_pointer_rtx, |
| GEN_INT (step2))); |
| |
| /* Add in the __builtin_eh_return stack adjustment. We need to |
| use a temporary in mips16 code. */ |
| if (current_function_calls_eh_return) |
| { |
| if (TARGET_MIPS16) |
| { |
| emit_move_insn (MIPS_EPILOGUE_TEMP (Pmode), stack_pointer_rtx); |
| emit_insn (gen_add3_insn (MIPS_EPILOGUE_TEMP (Pmode), |
| MIPS_EPILOGUE_TEMP (Pmode), |
| EH_RETURN_STACKADJ_RTX)); |
| emit_move_insn (stack_pointer_rtx, MIPS_EPILOGUE_TEMP (Pmode)); |
| } |
| else |
| emit_insn (gen_add3_insn (stack_pointer_rtx, |
| stack_pointer_rtx, |
| EH_RETURN_STACKADJ_RTX)); |
| } |
| |
| if (!sibcall_p) |
| { |
| /* The mips16 loads the return address into $7, not $31. */ |
| if (TARGET_MIPS16 && (cfun->machine->frame.mask & RA_MASK) != 0) |
| emit_jump_insn (gen_return_internal (gen_rtx_REG (Pmode, |
| GP_REG_FIRST + 7))); |
| else |
| emit_jump_insn (gen_return_internal (gen_rtx_REG (Pmode, |
| GP_REG_FIRST + 31))); |
| } |
| } |
| |
| /* Return nonzero if this function is known to have a null epilogue. |
| This allows the optimizer to omit jumps to jumps if no stack |
| was created. */ |
| |
| int |
| mips_can_use_return_insn (void) |
| { |
| tree return_type; |
| |
| if (! reload_completed) |
| return 0; |
| |
| if (regs_ever_live[31] || current_function_profile) |
| return 0; |
| |
| return_type = DECL_RESULT (current_function_decl); |
| |
| /* In mips16 mode, a function which returns a floating point value |
| needs to arrange to copy the return value into the floating point |
| registers. */ |
| if (TARGET_MIPS16 |
| && mips16_hard_float |
| && ! aggregate_value_p (return_type, current_function_decl) |
| && GET_MODE_CLASS (DECL_MODE (return_type)) == MODE_FLOAT |
| && GET_MODE_SIZE (DECL_MODE (return_type)) <= UNITS_PER_FPVALUE) |
| return 0; |
| |
| if (cfun->machine->frame.initialized) |
| return cfun->machine->frame.total_size == 0; |
| |
| return compute_frame_size (get_frame_size ()) == 0; |
| } |
| |
| /* Implement TARGET_ASM_OUTPUT_MI_THUNK. Generate rtl rather than asm text |
| in order to avoid duplicating too much logic from elsewhere. */ |
| |
| static void |
| mips_output_mi_thunk (FILE *file, tree thunk_fndecl ATTRIBUTE_UNUSED, |
| HOST_WIDE_INT delta, HOST_WIDE_INT vcall_offset, |
| tree function) |
| { |
| rtx this, temp1, temp2, insn, fnaddr; |
| |
| /* Pretend to be a post-reload pass while generating rtl. */ |
| no_new_pseudos = 1; |
| reload_completed = 1; |
| reset_block_changes (); |
| |
| /* Pick a global pointer for -mabicalls. Use $15 rather than $28 |
| for TARGET_NEWABI since the latter is a call-saved register. */ |
| if (TARGET_ABICALLS) |
| cfun->machine->global_pointer |
| = REGNO (pic_offset_table_rtx) |
| = TARGET_NEWABI ? 15 : GLOBAL_POINTER_REGNUM; |
| |
| /* Set up the global pointer for n32 or n64 abicalls. */ |
| mips_emit_loadgp (); |
| |
| /* We need two temporary registers in some cases. */ |
| temp1 = gen_rtx_REG (Pmode, 2); |
| temp2 = gen_rtx_REG (Pmode, 3); |
| |
| /* Find out which register contains the "this" pointer. */ |
| if (aggregate_value_p (TREE_TYPE (TREE_TYPE (function)), function)) |
| this = gen_rtx_REG (Pmode, GP_ARG_FIRST + 1); |
| else |
| this = gen_rtx_REG (Pmode, GP_ARG_FIRST); |
| |
| /* Add DELTA to THIS. */ |
| if (delta != 0) |
| { |
| rtx offset = GEN_INT (delta); |
| if (!SMALL_OPERAND (delta)) |
| { |
| emit_move_insn (temp1, offset); |
| offset = temp1; |
| } |
| emit_insn (gen_add3_insn (this, this, offset)); |
| } |
| |
| /* If needed, add *(*THIS + VCALL_OFFSET) to THIS. */ |
| if (vcall_offset != 0) |
| { |
| rtx addr; |
| |
| /* Set TEMP1 to *THIS. */ |
| emit_move_insn (temp1, gen_rtx_MEM (Pmode, this)); |
| |
| /* Set ADDR to a legitimate address for *THIS + VCALL_OFFSET. */ |
| addr = mips_add_offset (temp2, temp1, vcall_offset); |
| |
| /* Load the offset and add it to THIS. */ |
| emit_move_insn (temp1, gen_rtx_MEM (Pmode, addr)); |
| emit_insn (gen_add3_insn (this, this, temp1)); |
| } |
| |
| /* Jump to the target function. Use a sibcall if direct jumps are |
| allowed, otherwise load the address into a register first. */ |
| fnaddr = XEXP (DECL_RTL (function), 0); |
| if (TARGET_MIPS16 || TARGET_ABICALLS || TARGET_LONG_CALLS) |
| { |
| /* This is messy. gas treats "la $25,foo" as part of a call |
| sequence and may allow a global "foo" to be lazily bound. |
| The general move patterns therefore reject this combination. |
| |
| In this context, lazy binding would actually be OK for o32 and o64, |
| but it's still wrong for n32 and n64; see mips_load_call_address. |
| We must therefore load the address via a temporary register if |
| mips_dangerous_for_la25_p. |
| |
| If we jump to the temporary register rather than $25, the assembler |
| can use the move insn to fill the jump's delay slot. */ |
| if (TARGET_ABICALLS && !mips_dangerous_for_la25_p (fnaddr)) |
| temp1 = gen_rtx_REG (Pmode, PIC_FUNCTION_ADDR_REGNUM); |
| mips_load_call_address (temp1, fnaddr, true); |
| |
| if (TARGET_ABICALLS && REGNO (temp1) != PIC_FUNCTION_ADDR_REGNUM) |
| emit_move_insn (gen_rtx_REG (Pmode, PIC_FUNCTION_ADDR_REGNUM), temp1); |
| emit_jump_insn (gen_indirect_jump (temp1)); |
| } |
| else |
| { |
| insn = emit_call_insn (gen_sibcall_internal (fnaddr, const0_rtx)); |
| SIBLING_CALL_P (insn) = 1; |
| } |
| |
| /* Run just enough of rest_of_compilation. This sequence was |
| "borrowed" from alpha.c. */ |
| insn = get_insns (); |
| insn_locators_initialize (); |
| split_all_insns_noflow (); |
| if (TARGET_MIPS16) |
| mips16_lay_out_constants (); |
| shorten_branches (insn); |
| final_start_function (insn, file, 1); |
| final (insn, file, 1, 0); |
| final_end_function (); |
| |
| /* Clean up the vars set above. Note that final_end_function resets |
| the global pointer for us. */ |
| reload_completed = 0; |
| no_new_pseudos = 0; |
| } |
| |
| /* Returns nonzero if X contains a SYMBOL_REF. */ |
| |
| static int |
| symbolic_expression_p (rtx x) |
| { |
| if (GET_CODE (x) == SYMBOL_REF) |
| return 1; |
| |
| if (GET_CODE (x) == CONST) |
| return symbolic_expression_p (XEXP (x, 0)); |
| |
| if (UNARY_P (x)) |
| return symbolic_expression_p (XEXP (x, 0)); |
| |
| if (ARITHMETIC_P (x)) |
| return (symbolic_expression_p (XEXP (x, 0)) |
| || symbolic_expression_p (XEXP (x, 1))); |
| |
| return 0; |
| } |
| |
| /* Choose the section to use for the constant rtx expression X that has |
| mode MODE. */ |
| |
| static void |
| mips_select_rtx_section (enum machine_mode mode, rtx x, |
| unsigned HOST_WIDE_INT align) |
| { |
| if (TARGET_MIPS16) |
| { |
| /* In mips16 mode, the constant table always goes in the same section |
| as the function, so that constants can be loaded using PC relative |
| addressing. */ |
| function_section (current_function_decl); |
| } |
| else if (TARGET_EMBEDDED_DATA) |
| { |
| /* For embedded applications, always put constants in read-only data, |
| in order to reduce RAM usage. */ |
| mergeable_constant_section (mode, align, 0); |
| } |
| else |
| { |
| /* For hosted applications, always put constants in small data if |
| possible, as this gives the best performance. */ |
| /* ??? Consider using mergeable small data sections. */ |
| |
| if (GET_MODE_SIZE (mode) <= (unsigned) mips_section_threshold |
| && mips_section_threshold > 0) |
| named_section (0, ".sdata", 0); |
| else if (flag_pic && symbolic_expression_p (x)) |
| named_section (0, ".data.rel.ro", 3); |
| else |
| mergeable_constant_section (mode, align, 0); |
| } |
| } |
| |
| /* Implement TARGET_ASM_FUNCTION_RODATA_SECTION. |
| |
| The complication here is that, with the combination TARGET_ABICALLS |
| && !TARGET_GPWORD, jump tables will use absolute addresses, and should |
| therefore not be included in the read-only part of a DSO. Handle such |
| cases by selecting a normal data section instead of a read-only one. |
| The logic apes that in default_function_rodata_section. */ |
| |
| static void |
| mips_function_rodata_section (tree decl) |
| { |
| if (!TARGET_ABICALLS || TARGET_GPWORD) |
| default_function_rodata_section (decl); |
| else if (decl && DECL_SECTION_NAME (decl)) |
| { |
| const char *name = TREE_STRING_POINTER (DECL_SECTION_NAME (decl)); |
| if (DECL_ONE_ONLY (decl) && strncmp (name, ".gnu.linkonce.t.", 16) == 0) |
| { |
| char *rname = ASTRDUP (name); |
| rname[14] = 'd'; |
| named_section_real (rname, SECTION_LINKONCE | SECTION_WRITE, decl); |
| } |
| else if (flag_function_sections && flag_data_sections |
| && strncmp (name, ".text.", 6) == 0) |
| { |
| char *rname = ASTRDUP (name); |
| memcpy (rname + 1, "data", 4); |
| named_section_flags (rname, SECTION_WRITE); |
| } |
| else |
| data_section (); |
| } |
| else |
| data_section (); |
| } |
| |
| /* Implement TARGET_IN_SMALL_DATA_P. Return true if it would be safe to |
| access DECL using %gp_rel(...)($gp). */ |
| |
| static bool |
| mips_in_small_data_p (tree decl) |
| { |
| HOST_WIDE_INT size; |
| |
| if (TREE_CODE (decl) == STRING_CST || TREE_CODE (decl) == FUNCTION_DECL) |
| return false; |
| |
| /* We don't yet generate small-data references for -mabicalls. See related |
| -G handling in override_options. */ |
| if (TARGET_ABICALLS) |
| return false; |
| |
| if (TREE_CODE (decl) == VAR_DECL && DECL_SECTION_NAME (decl) != 0) |
| { |
| const char *name; |
| |
| /* Reject anything that isn't in a known small-data section. */ |
| name = TREE_STRING_POINTER (DECL_SECTION_NAME (decl)); |
| if (strcmp (name, ".sdata") != 0 && strcmp (name, ".sbss") != 0) |
| return false; |
| |
| /* If a symbol is defined externally, the assembler will use the |
| usual -G rules when deciding how to implement macros. */ |
| if (TARGET_EXPLICIT_RELOCS || !DECL_EXTERNAL (decl)) |
| return true; |
| } |
| else if (TARGET_EMBEDDED_DATA) |
| { |
| /* Don't put constants into the small data section: we want them |
| to be in ROM rather than RAM. */ |
| if (TREE_CODE (decl) != VAR_DECL) |
| return false; |
| |
| if (TREE_READONLY (decl) |
| && !TREE_SIDE_EFFECTS (decl) |
| && (!DECL_INITIAL (decl) || TREE_CONSTANT (DECL_INITIAL (decl)))) |
| return false; |
| } |
| |
| size = int_size_in_bytes (TREE_TYPE (decl)); |
| return (size > 0 && size <= mips_section_threshold); |
| } |
| |
| /* See whether VALTYPE is a record whose fields should be returned in |
| floating-point registers. If so, return the number of fields and |
| list them in FIELDS (which should have two elements). Return 0 |
| otherwise. |
| |
| For n32 & n64, a structure with one or two fields is returned in |
| floating-point registers as long as every field has a floating-point |
| type. */ |
| |
| static int |
| mips_fpr_return_fields (tree valtype, tree *fields) |
| { |
| tree field; |
| int i; |
| |
| if (!TARGET_NEWABI) |
| return 0; |
| |
| if (TREE_CODE (valtype) != RECORD_TYPE) |
| return 0; |
| |
| i = 0; |
| for (field = TYPE_FIELDS (valtype); field != 0; field = TREE_CHAIN (field)) |
| { |
| if (TREE_CODE (field) != FIELD_DECL) |
| continue; |
| |
| if (TREE_CODE (TREE_TYPE (field)) != REAL_TYPE) |
| return 0; |
| |
| if (i == 2) |
| return 0; |
| |
| fields[i++] = field; |
| } |
| return i; |
| } |
| |
| |
| /* Implement TARGET_RETURN_IN_MSB. For n32 & n64, we should return |
| a value in the most significant part of $2/$3 if: |
| |
| - the target is big-endian; |
| |
| - the value has a structure or union type (we generalize this to |
| cover aggregates from other languages too); and |
| |
| - the structure is not returned in floating-point registers. */ |
| |
| static bool |
| mips_return_in_msb (tree valtype) |
| { |
| tree fields[2]; |
| |
| return (TARGET_NEWABI |
| && TARGET_BIG_ENDIAN |
| && AGGREGATE_TYPE_P (valtype) |
| && mips_fpr_return_fields (valtype, fields) == 0); |
| } |
| |
| |
| /* Return a composite value in a pair of floating-point registers. |
| MODE1 and OFFSET1 are the mode and byte offset for the first value, |
| likewise MODE2 and OFFSET2 for the second. MODE is the mode of the |
| complete value. |
| |
| For n32 & n64, $f0 always holds the first value and $f2 the second. |
| Otherwise the values are packed together as closely as possible. */ |
| |
| static rtx |
| mips_return_fpr_pair (enum machine_mode mode, |
| enum machine_mode mode1, HOST_WIDE_INT offset1, |
| enum machine_mode mode2, HOST_WIDE_INT offset2) |
| { |
| int inc; |
| |
| inc = (TARGET_NEWABI ? 2 : FP_INC); |
| return gen_rtx_PARALLEL |
| (mode, |
| gen_rtvec (2, |
| gen_rtx_EXPR_LIST (VOIDmode, |
| gen_rtx_REG (mode1, FP_RETURN), |
| GEN_INT (offset1)), |
| gen_rtx_EXPR_LIST (VOIDmode, |
| gen_rtx_REG (mode2, FP_RETURN + inc), |
| GEN_INT (offset2)))); |
| |
| } |
| |
| |
| /* Implement FUNCTION_VALUE and LIBCALL_VALUE. For normal calls, |
| VALTYPE is the return type and MODE is VOIDmode. For libcalls, |
| VALTYPE is null and MODE is the mode of the return value. */ |
| |
| rtx |
| mips_function_value (tree valtype, tree func ATTRIBUTE_UNUSED, |
| enum machine_mode mode) |
| { |
| if (valtype) |
| { |
| tree fields[2]; |
| int unsignedp; |
| |
| mode = TYPE_MODE (valtype); |
| unsignedp = TYPE_UNSIGNED (valtype); |
| |
| /* Since we define TARGET_PROMOTE_FUNCTION_RETURN that returns |
| true, we must promote the mode just as PROMOTE_MODE does. */ |
| mode = promote_mode (valtype, mode, &unsignedp, 1); |
| |
| /* Handle structures whose fields are returned in $f0/$f2. */ |
| switch (mips_fpr_return_fields (valtype, fields)) |
| { |
| case 1: |
| return gen_rtx_REG (mode, FP_RETURN); |
| |
| case 2: |
| return mips_return_fpr_pair (mode, |
| TYPE_MODE (TREE_TYPE (fields[0])), |
| int_byte_position (fields[0]), |
| TYPE_MODE (TREE_TYPE (fields[1])), |
| int_byte_position (fields[1])); |
| } |
| |
| /* If a value is passed in the most significant part of a register, see |
| whether we have to round the mode up to a whole number of words. */ |
| if (mips_return_in_msb (valtype)) |
| { |
| HOST_WIDE_INT size = int_size_in_bytes (valtype); |
| if (size % UNITS_PER_WORD != 0) |
| { |
| size += UNITS_PER_WORD - size % UNITS_PER_WORD; |
| mode = mode_for_size (size * BITS_PER_UNIT, MODE_INT, 0); |
| } |
| } |
| |
| /* For EABI, the class of return register depends entirely on MODE. |
| For example, "struct { some_type x; }" and "union { some_type x; }" |
| are returned in the same way as a bare "some_type" would be. |
| Other ABIs only use FPRs for scalar, complex or vector types. */ |
| if (mips_abi != ABI_EABI && !FLOAT_TYPE_P (valtype)) |
| return gen_rtx_REG (mode, GP_RETURN); |
| } |
| |
| if ((GET_MODE_CLASS (mode) == MODE_FLOAT |
| || GET_MODE_CLASS (mode) == MODE_VECTOR_FLOAT) |
| && GET_MODE_SIZE (mode) <= UNITS_PER_HWFPVALUE) |
| return gen_rtx_REG (mode, FP_RETURN); |
| |
| /* Handle long doubles for n32 & n64. */ |
| if (mode == TFmode) |
| return mips_return_fpr_pair (mode, |
| DImode, 0, |
| DImode, GET_MODE_SIZE (mode) / 2); |
| |
| if (GET_MODE_CLASS (mode) == MODE_COMPLEX_FLOAT |
| && GET_MODE_SIZE (mode) <= UNITS_PER_HWFPVALUE * 2) |
| return mips_return_fpr_pair (mode, |
| GET_MODE_INNER (mode), 0, |
| GET_MODE_INNER (mode), |
| GET_MODE_SIZE (mode) / 2); |
| |
| return gen_rtx_REG (mode, GP_RETURN); |
| } |
| |
| /* Return nonzero when an argument must be passed by reference. */ |
| |
| static bool |
| mips_pass_by_reference (CUMULATIVE_ARGS *cum ATTRIBUTE_UNUSED, |
| enum machine_mode mode, tree type, |
| bool named ATTRIBUTE_UNUSED) |
| { |
| if (mips_abi == ABI_EABI) |
| { |
| int size; |
| |
| /* ??? How should SCmode be handled? */ |
| if (type == NULL_TREE || mode == DImode || mode == DFmode) |
| return 0; |
| |
| size = int_size_in_bytes (type); |
| return size == -1 || size > UNITS_PER_WORD; |
| } |
| else |
| { |
| /* If we have a variable-sized parameter, we have no choice. */ |
| return targetm.calls.must_pass_in_stack (mode, type); |
| } |
| } |
| |
| static bool |
| mips_callee_copies (CUMULATIVE_ARGS *cum ATTRIBUTE_UNUSED, |
| enum machine_mode mode ATTRIBUTE_UNUSED, |
| tree type ATTRIBUTE_UNUSED, bool named) |
| { |
| return mips_abi == ABI_EABI && named; |
| } |
| |
| /* Return true if registers of class CLASS cannot change from mode FROM |
| to mode TO. */ |
| |
| bool |
| mips_cannot_change_mode_class (enum machine_mode from, |
| enum machine_mode to, enum reg_class class) |
| { |
| if (MIN (GET_MODE_SIZE (from), GET_MODE_SIZE (to)) <= UNITS_PER_WORD |
| && MAX (GET_MODE_SIZE (from), GET_MODE_SIZE (to)) > UNITS_PER_WORD) |
| { |
| if (TARGET_BIG_ENDIAN) |
| { |
| /* When a multi-word value is stored in paired floating-point |
| registers, the first register always holds the low word. |
| We therefore can't allow FPRs to change between single-word |
| and multi-word modes. */ |
| if (FP_INC > 1 && reg_classes_intersect_p (FP_REGS, class)) |
| return true; |
| } |
| else |
| { |
| /* LO_REGNO == HI_REGNO + 1, so if a multi-word value is stored |
| in LO and HI, the high word always comes first. We therefore |
| can't allow values stored in HI to change between single-word |
| and multi-word modes. */ |
| if (reg_classes_intersect_p (HI_REG, class)) |
| return true; |
| } |
| } |
| /* Loading a 32-bit value into a 64-bit floating-point register |
| will not sign-extend the value, despite what LOAD_EXTEND_OP says. |
| We can't allow 64-bit float registers to change from SImode to |
| to a wider mode. */ |
| if (TARGET_FLOAT64 |
| && from == SImode |
| && GET_MODE_SIZE (to) >= UNITS_PER_WORD |
| && reg_classes_intersect_p (FP_REGS, class)) |
| return true; |
| return false; |
| } |
| |
| /* Return true if X should not be moved directly into register $25. |
| We need this because many versions of GAS will treat "la $25,foo" as |
| part of a call sequence and so allow a global "foo" to be lazily bound. */ |
| |
| bool |
| mips_dangerous_for_la25_p (rtx x) |
| { |
| HOST_WIDE_INT offset; |
| |
| if (TARGET_EXPLICIT_RELOCS) |
| return false; |
| |
| mips_split_const (x, &x, &offset); |
| return global_got_operand (x, VOIDmode); |
| } |
| |
| /* Implement PREFERRED_RELOAD_CLASS. */ |
| |
| enum reg_class |
| mips_preferred_reload_class (rtx x, enum reg_class class) |
| { |
| if (mips_dangerous_for_la25_p (x) && reg_class_subset_p (LEA_REGS, class)) |
| return LEA_REGS; |
| |
| if (TARGET_HARD_FLOAT |
| && FLOAT_MODE_P (GET_MODE (x)) |
| && reg_class_subset_p (FP_REGS, class)) |
| return FP_REGS; |
| |
| if (reg_class_subset_p (GR_REGS, class)) |
| class = GR_REGS; |
| |
| if (TARGET_MIPS16 && reg_class_subset_p (M16_REGS, class)) |
| class = M16_REGS; |
| |
| return class; |
| } |
| |
| /* This function returns the register class required for a secondary |
| register when copying between one of the registers in CLASS, and X, |
| using MODE. If IN_P is nonzero, the copy is going from X to the |
| register, otherwise the register is the source. A return value of |
| NO_REGS means that no secondary register is required. */ |
| |
| enum reg_class |
| mips_secondary_reload_class (enum reg_class class, |
| enum machine_mode mode, rtx x, int in_p) |
| { |
| enum reg_class gr_regs = TARGET_MIPS16 ? M16_REGS : GR_REGS; |
| int regno = -1; |
| int gp_reg_p; |
| |
| if (REG_P (x)|| GET_CODE (x) == SUBREG) |
| regno = true_regnum (x); |
| |
| gp_reg_p = TARGET_MIPS16 ? M16_REG_P (regno) : GP_REG_P (regno); |
| |
| if (mips_dangerous_for_la25_p (x)) |
| { |
| gr_regs = LEA_REGS; |
| if (TEST_HARD_REG_BIT (reg_class_contents[(int) class], 25)) |
| return gr_regs; |
| } |
| |
| /* Copying from HI or LO to anywhere other than a general register |
| requires a general register. */ |
| if (class == HI_REG || class == LO_REG || class == MD_REGS) |
| { |
| if (TARGET_MIPS16 && in_p) |
| { |
| /* We can't really copy to HI or LO at all in mips16 mode. */ |
| return M16_REGS; |
| } |
| return gp_reg_p ? NO_REGS : gr_regs; |
| } |
| if (MD_REG_P (regno)) |
| { |
| if (TARGET_MIPS16 && ! in_p) |
| { |
| /* We can't really copy to HI or LO at all in mips16 mode. */ |
| return M16_REGS; |
| } |
| return class == gr_regs ? NO_REGS : gr_regs; |
| } |
| |
| /* We can only copy a value to a condition code register from a |
| floating point register, and even then we require a scratch |
| floating point register. We can only copy a value out of a |
| condition code register into a general register. */ |
| if (class == ST_REGS) |
| { |
| if (in_p) |
| return FP_REGS; |
| return gp_reg_p ? NO_REGS : gr_regs; |
| } |
| if (ST_REG_P (regno)) |
| { |
| if (! in_p) |
| return FP_REGS; |
| return class == gr_regs ? NO_REGS : gr_regs; |
| } |
| |
| if (class == FP_REGS) |
| { |
| if (MEM_P (x)) |
| { |
| /* In this case we can use lwc1, swc1, ldc1 or sdc1. */ |
| return NO_REGS; |
| } |
| else if (CONSTANT_P (x) && GET_MODE_CLASS (mode) == MODE_FLOAT) |
| { |
| /* We can use the l.s and l.d macros to load floating-point |
| constants. ??? For l.s, we could probably get better |
| code by returning GR_REGS here. */ |
| return NO_REGS; |
| } |
| else if (gp_reg_p || x == CONST0_RTX (mode)) |
| { |
| /* In this case we can use mtc1, mfc1, dmtc1 or dmfc1. */ |
| return NO_REGS; |
| } |
| else if (FP_REG_P (regno)) |
| { |
| /* In this case we can use mov.s or mov.d. */ |
| return NO_REGS; |
| } |
| else |
| { |
| /* Otherwise, we need to reload through an integer register. */ |
| return gr_regs; |
| } |
| } |
| |
| /* In mips16 mode, going between memory and anything but M16_REGS |
| requires an M16_REG. */ |
| if (TARGET_MIPS16) |
| { |
| if (class != M16_REGS && class != M16_NA_REGS) |
| { |
| if (gp_reg_p) |
| return NO_REGS; |
| return M16_REGS; |
| } |
| if (! gp_reg_p) |
| { |
| if (class == M16_REGS || class == M16_NA_REGS) |
| return NO_REGS; |
| return M16_REGS; |
| } |
| } |
| |
| return NO_REGS; |
| } |
| |
| /* Implement CLASS_MAX_NREGS. |
| |
| Usually all registers are word-sized. The only supported exception |
| is -mgp64 -msingle-float, which has 64-bit words but 32-bit float |
| registers. A word-based calculation is correct even in that case, |
| since -msingle-float disallows multi-FPR values. |
| |
| The FP status registers are an exception to this rule. They are always |
| 4 bytes wide as they only hold condition code modes, and CCmode is always |
| considered to be 4 bytes wide. */ |
| |
| int |
| mips_class_max_nregs (enum reg_class class ATTRIBUTE_UNUSED, |
| enum machine_mode mode) |
| { |
| if (class == ST_REGS) |
| return (GET_MODE_SIZE (mode) + 3) / 4; |
| else |
| return (GET_MODE_SIZE (mode) + UNITS_PER_WORD - 1) / UNITS_PER_WORD; |
| } |
| |
| static bool |
| mips_valid_pointer_mode (enum machine_mode mode) |
| { |
| return (mode == SImode || (TARGET_64BIT && mode == DImode)); |
| } |
| |
| /* Define this so that we can deal with a testcase like: |
| |
| char foo __attribute__ ((mode (SI))); |
| |
| then compiled with -mabi=64 and -mint64. We have no |
| 32-bit type at that point and so the default case |
| always fails. */ |
| |
| static bool |
| mips_scalar_mode_supported_p (enum machine_mode mode) |
| { |
| switch (mode) |
| { |
| case QImode: |
| case HImode: |
| case SImode: |
| case DImode: |
| return true; |
| |
| /* Handled via optabs.c. */ |
| case TImode: |
| return TARGET_64BIT; |
| |
| case SFmode: |
| case DFmode: |
| return true; |
| |
| /* LONG_DOUBLE_TYPE_SIZE is 128 for TARGET_NEWABI only. */ |
| case TFmode: |
| return TARGET_NEWABI; |
| |
| default: |
| return false; |
| } |
| } |
| |
| |
| /* Target hook for vector_mode_supported_p. */ |
| static bool |
| mips_vector_mode_supported_p (enum machine_mode mode) |
| { |
| if (mode == V2SFmode && TARGET_PAIRED_SINGLE_FLOAT) |
| return true; |
| else |
| return false; |
| } |
| |
| /* If we can access small data directly (using gp-relative relocation |
| operators) return the small data pointer, otherwise return null. |
| |
| For each mips16 function which refers to GP relative symbols, we |
| use a pseudo register, initialized at the start of the function, to |
| hold the $gp value. */ |
| |
| static rtx |
| mips16_gp_pseudo_reg (void) |
| { |
| if (cfun->machine->mips16_gp_pseudo_rtx == NULL_RTX) |
| { |
| rtx unspec; |
| rtx insn, scan; |
| |
| cfun->machine->mips16_gp_pseudo_rtx = gen_reg_rtx (Pmode); |
| |
| /* We want to initialize this to a value which gcc will believe |
| is constant. */ |
| start_sequence (); |
| unspec = gen_rtx_UNSPEC (VOIDmode, gen_rtvec (1, const0_rtx), UNSPEC_GP); |
| emit_move_insn (cfun->machine->mips16_gp_pseudo_rtx, |
| gen_rtx_CONST (Pmode, unspec)); |
| insn = get_insns (); |
| end_sequence (); |
| |
| push_topmost_sequence (); |
| /* We need to emit the initialization after the FUNCTION_BEG |
| note, so that it will be integrated. */ |
| for (scan = get_insns (); scan != NULL_RTX; scan = NEXT_INSN (scan)) |
| if (NOTE_P (scan) |
| && NOTE_LINE_NUMBER (scan) == NOTE_INSN_FUNCTION_BEG) |
| break; |
| if (scan == NULL_RTX) |
| scan = get_insns (); |
| insn = emit_insn_after (insn, scan); |
| pop_topmost_sequence (); |
| } |
| |
| return cfun->machine->mips16_gp_pseudo_rtx; |
| } |
| |
| /* Write out code to move floating point arguments in or out of |
| general registers. Output the instructions to FILE. FP_CODE is |
| the code describing which arguments are present (see the comment at |
| the definition of CUMULATIVE_ARGS in mips.h). FROM_FP_P is nonzero if |
| we are copying from the floating point registers. */ |
| |
| static void |
| mips16_fp_args (FILE *file, int fp_code, int from_fp_p) |
| { |
| const char *s; |
| int gparg, fparg; |
| unsigned int f; |
| |
| /* This code only works for the original 32 bit ABI and the O64 ABI. */ |
| gcc_assert (TARGET_OLDABI); |
| |
| if (from_fp_p) |
| s = "mfc1"; |
| else |
| s = "mtc1"; |
| gparg = GP_ARG_FIRST; |
| fparg = FP_ARG_FIRST; |
| for (f = (unsigned int) fp_code; f != 0; f >>= 2) |
| { |
| if ((f & 3) == 1) |
| { |
| if ((fparg & 1) != 0) |
| ++fparg; |
| fprintf (file, "\t%s\t%s,%s\n", s, |
| reg_names[gparg], reg_names[fparg]); |
| } |
| else if ((f & 3) == 2) |
| { |
| if (TARGET_64BIT) |
| fprintf (file, "\td%s\t%s,%s\n", s, |
| reg_names[gparg], reg_names[fparg]); |
| else |
| { |
| if ((fparg & 1) != 0) |
| ++fparg; |
| if (TARGET_BIG_ENDIAN) |
| fprintf (file, "\t%s\t%s,%s\n\t%s\t%s,%s\n", s, |
| reg_names[gparg], reg_names[fparg + 1], s, |
| reg_names[gparg + 1], reg_names[fparg]); |
| else |
| fprintf (file, "\t%s\t%s,%s\n\t%s\t%s,%s\n", s, |
| reg_names[gparg], reg_names[fparg], s, |
| reg_names[gparg + 1], reg_names[fparg + 1]); |
| ++gparg; |
| ++fparg; |
| } |
| } |
| else |
| gcc_unreachable (); |
| |
| ++gparg; |
| ++fparg; |
| } |
| } |
| |
| /* Build a mips16 function stub. This is used for functions which |
| take arguments in the floating point registers. It is 32 bit code |
| that moves the floating point args into the general registers, and |
| then jumps to the 16 bit code. */ |
| |
| static void |
| build_mips16_function_stub (FILE *file) |
| { |
| const char *fnname; |
| char *secname, *stubname; |
| tree stubid, stubdecl; |
| int need_comma; |
| unsigned int f; |
| |
| fnname = XSTR (XEXP (DECL_RTL (current_function_decl), 0), 0); |
| secname = (char *) alloca (strlen (fnname) + 20); |
| sprintf (secname, ".mips16.fn.%s", fnname); |
| stubname = (char *) alloca (strlen (fnname) + 20); |
| sprintf (stubname, "__fn_stub_%s", fnname); |
| stubid = get_identifier (stubname); |
| stubdecl = build_decl (FUNCTION_DECL, stubid, |
| build_function_type (void_type_node, NULL_TREE)); |
| DECL_SECTION_NAME (stubdecl) = build_string (strlen (secname), secname); |
| |
| fprintf (file, "\t# Stub function for %s (", current_function_name ()); |
| need_comma = 0; |
| for (f = (unsigned int) current_function_args_info.fp_code; f != 0; f >>= 2) |
| { |
| fprintf (file, "%s%s", |
| need_comma ? ", " : "", |
| (f & 3) == 1 ? "float" : "double"); |
| need_comma = 1; |
| } |
| fprintf (file, ")\n"); |
| |
| fprintf (file, "\t.set\tnomips16\n"); |
| function_section (stubdecl); |
| ASM_OUTPUT_ALIGN (file, floor_log2 (FUNCTION_BOUNDARY / BITS_PER_UNIT)); |
| |
| /* ??? If FUNCTION_NAME_ALREADY_DECLARED is defined, then we are |
| within a .ent, and we cannot emit another .ent. */ |
| if (!FUNCTION_NAME_ALREADY_DECLARED) |
| { |
| fputs ("\t.ent\t", file); |
| assemble_name (file, stubname); |
| fputs ("\n", file); |
| } |
| |
| assemble_name (file, stubname); |
| fputs (":\n", file); |
| |
| /* We don't want the assembler to insert any nops here. */ |
| fprintf (file, "\t.set\tnoreorder\n"); |
| |
| mips16_fp_args (file, current_function_args_info.fp_code, 1); |
| |
| fprintf (asm_out_file, "\t.set\tnoat\n"); |
| fprintf (asm_out_file, "\tla\t%s,", reg_names[GP_REG_FIRST + 1]); |
| assemble_name (file, fnname); |
| fprintf (file, "\n"); |
| fprintf (asm_out_file, "\tjr\t%s\n", reg_names[GP_REG_FIRST + 1]); |
| fprintf (asm_out_file, "\t.set\tat\n"); |
| |
| /* Unfortunately, we can't fill the jump delay slot. We can't fill |
| with one of the mfc1 instructions, because the result is not |
| available for one instruction, so if the very first instruction |
| in the function refers to the register, it will see the wrong |
| value. */ |
| fprintf (file, "\tnop\n"); |
| |
| fprintf (file, "\t.set\treorder\n"); |
| |
| if (!FUNCTION_NAME_ALREADY_DECLARED) |
| { |
| fputs ("\t.end\t", file); |
| assemble_name (file, stubname); |
| fputs ("\n", file); |
| } |
| |
| fprintf (file, "\t.set\tmips16\n"); |
| |
| function_section (current_function_decl); |
| } |
| |
| /* We keep a list of functions for which we have already built stubs |
| in build_mips16_call_stub. */ |
| |
| struct mips16_stub |
| { |
| struct mips16_stub *next; |
| char *name; |
| int fpret; |
| }; |
| |
| static struct mips16_stub *mips16_stubs; |
| |
| /* Build a call stub for a mips16 call. A stub is needed if we are |
| passing any floating point values which should go into the floating |
| point registers. If we are, and the call turns out to be to a 32 |
| bit function, the stub will be used to move the values into the |
| floating point registers before calling the 32 bit function. The |
| linker will magically adjust the function call to either the 16 bit |
| function or the 32 bit stub, depending upon where the function call |
| is actually defined. |
| |
| Similarly, we need a stub if the return value might come back in a |
| floating point register. |
| |
| RETVAL is the location of the return value, or null if this is |
| a call rather than a call_value. FN is the address of the |
| function and ARG_SIZE is the size of the arguments. FP_CODE |
| is the code built by function_arg. This function returns a nonzero |
| value if it builds the call instruction itself. */ |
| |
| int |
| build_mips16_call_stub (rtx retval, rtx fn, rtx arg_size, int fp_code) |
| { |
| int fpret; |
| const char *fnname; |
| char *secname, *stubname; |
| struct mips16_stub *l; |
| tree stubid, stubdecl; |
| int need_comma; |
| unsigned int f; |
| |
| /* We don't need to do anything if we aren't in mips16 mode, or if |
| we were invoked with the -msoft-float option. */ |
| if (! TARGET_MIPS16 || ! mips16_hard_float) |
| return 0; |
| |
| /* Figure out whether the value might come back in a floating point |
| register. */ |
| fpret = (retval != 0 |
| && GET_MODE_CLASS (GET_MODE (retval)) == MODE_FLOAT |
| && GET_MODE_SIZE (GET_MODE (retval)) <= UNITS_PER_FPVALUE); |
| |
| /* We don't need to do anything if there were no floating point |
| arguments and the value will not be returned in a floating point |
| register. */ |
| if (fp_code == 0 && ! fpret) |
| return 0; |
| |
| /* We don't need to do anything if this is a call to a special |
| mips16 support function. */ |
| if (GET_CODE (fn) == SYMBOL_REF |
| && strncmp (XSTR (fn, 0), "__mips16_", 9) == 0) |
| return 0; |
| |
| /* This code will only work for o32 and o64 abis. The other ABI's |
| require more sophisticated support. */ |
| gcc_assert (TARGET_OLDABI); |
| |
| /* We can only handle SFmode and DFmode floating point return |
| values. */ |
| if (fpret) |
| gcc_assert (GET_MODE (retval) == SFmode || GET_MODE (retval) == DFmode); |
| |
| /* If we're calling via a function pointer, then we must always call |
| via a stub. There are magic stubs provided in libgcc.a for each |
| of the required cases. Each of them expects the function address |
| to arrive in register $2. */ |
| |
| if (GET_CODE (fn) != SYMBOL_REF) |
| { |
| char buf[30]; |
| tree id; |
| rtx stub_fn, insn; |
| |
| /* ??? If this code is modified to support other ABI's, we need |
| to handle PARALLEL return values here. */ |
| |
| sprintf (buf, "__mips16_call_stub_%s%d", |
| (fpret |
| ? (GET_MODE (retval) == SFmode ? "sf_" : "df_") |
| : ""), |
| fp_code); |
| id = get_identifier (buf); |
| stub_fn = gen_rtx_SYMBOL_REF (Pmode, IDENTIFIER_POINTER (id)); |
| |
| emit_move_insn (gen_rtx_REG (Pmode, 2), fn); |
| |
| if (retval == NULL_RTX) |
| insn = gen_call_internal (stub_fn, arg_size); |
| else |
| insn = gen_call_value_internal (retval, stub_fn, arg_size); |
| insn = emit_call_insn (insn); |
| |
| /* Put the register usage information on the CALL. */ |
| CALL_INSN_FUNCTION_USAGE (insn) = |
| gen_rtx_EXPR_LIST (VOIDmode, |
| gen_rtx_USE (VOIDmode, gen_rtx_REG (Pmode, 2)), |
| CALL_INSN_FUNCTION_USAGE (insn)); |
| |
| /* If we are handling a floating point return value, we need to |
| save $18 in the function prologue. Putting a note on the |
| call will mean that regs_ever_live[$18] will be true if the |
| call is not eliminated, and we can check that in the prologue |
| code. */ |
| if (fpret) |
| CALL_INSN_FUNCTION_USAGE (insn) = |
| gen_rtx_EXPR_LIST (VOIDmode, |
| gen_rtx_USE (VOIDmode, |
| gen_rtx_REG (word_mode, 18)), |
| CALL_INSN_FUNCTION_USAGE (insn)); |
| |
| /* Return 1 to tell the caller that we've generated the call |
| insn. */ |
| return 1; |
| } |
| |
| /* We know the function we are going to call. If we have already |
| built a stub, we don't need to do anything further. */ |
| |
| fnname = XSTR (fn, 0); |
| for (l = mips16_stubs; l != NULL; l = l->next) |
| if (strcmp (l->name, fnname) == 0) |
| break; |
| |
| if (l == NULL) |
| { |
| /* Build a special purpose stub. When the linker sees a |
| function call in mips16 code, it will check where the target |
| is defined. If the target is a 32 bit call, the linker will |
| search for the section defined here. It can tell which |
| symbol this section is associated with by looking at the |
| relocation information (the name is unreliable, since this |
| might be a static function). If such a section is found, the |
| linker will redirect the call to the start of the magic |
| section. |
| |
| If the function does not return a floating point value, the |
| special stub section is named |
| .mips16.call.FNNAME |
| |
| If the function does return a floating point value, the stub |
| section is named |
| .mips16.call.fp.FNNAME |
| */ |
| |
| secname = (char *) alloca (strlen (fnname) + 40); |
| sprintf (secname, ".mips16.call.%s%s", |
| fpret ? "fp." : "", |
| fnname); |
| stubname = (char *) alloca (strlen (fnname) + 20); |
| sprintf (stubname, "__call_stub_%s%s", |
| fpret ? "fp_" : "", |
| fnname); |
| stubid = get_identifier (stubname); |
| stubdecl = build_decl (FUNCTION_DECL, stubid, |
| build_function_type (void_type_node, NULL_TREE)); |
| DECL_SECTION_NAME (stubdecl) = build_string (strlen (secname), secname); |
| |
| fprintf (asm_out_file, "\t# Stub function to call %s%s (", |
| (fpret |
| ? (GET_MODE (retval) == SFmode ? "float " : "double ") |
| : ""), |
| fnname); |
| need_comma = 0; |
| for (f = (unsigned int) fp_code; f != 0; f >>= 2) |
| { |
| fprintf (asm_out_file, "%s%s", |
| need_comma ? ", " : "", |
| (f & 3) == 1 ? "float" : "double"); |
| need_comma = 1; |
| } |
| fprintf (asm_out_file, ")\n"); |
| |
| fprintf (asm_out_file, "\t.set\tnomips16\n"); |
| assemble_start_function (stubdecl, stubname); |
| |
| if (!FUNCTION_NAME_ALREADY_DECLARED) |
| { |
| fputs ("\t.ent\t", asm_out_file); |
| assemble_name (asm_out_file, stubname); |
| fputs ("\n", asm_out_file); |
| |
| assemble_name (asm_out_file, stubname); |
| fputs (":\n", asm_out_file); |
| } |
| |
| /* We build the stub code by hand. That's the only way we can |
| do it, since we can't generate 32 bit code during a 16 bit |
| compilation. */ |
| |
| /* We don't want the assembler to insert any nops here. */ |
| fprintf (asm_out_file, "\t.set\tnoreorder\n"); |
| |
| mips16_fp_args (asm_out_file, fp_code, 0); |
| |
| if (! fpret) |
| { |
| fprintf (asm_out_file, "\t.set\tnoat\n"); |
| fprintf (asm_out_file, "\tla\t%s,%s\n", reg_names[GP_REG_FIRST + 1], |
| fnname); |
| fprintf (asm_out_file, "\tjr\t%s\n", reg_names[GP_REG_FIRST + 1]); |
| fprintf (asm_out_file, "\t.set\tat\n"); |
| /* Unfortunately, we can't fill the jump delay slot. We |
| can't fill with one of the mtc1 instructions, because the |
| result is not available for one instruction, so if the |
| very first instruction in the function refers to the |
| register, it will see the wrong value. */ |
| fprintf (asm_out_file, "\tnop\n"); |
| } |
| else |
| { |
| fprintf (asm_out_file, "\tmove\t%s,%s\n", |
| reg_names[GP_REG_FIRST + 18], reg_names[GP_REG_FIRST + 31]); |
| fprintf (asm_out_file, "\tjal\t%s\n", fnname); |
| /* As above, we can't fill the delay slot. */ |
| fprintf (asm_out_file, "\tnop\n"); |
| if (GET_MODE (retval) == SFmode) |
| fprintf (asm_out_file, "\tmfc1\t%s,%s\n", |
| reg_names[GP_REG_FIRST + 2], reg_names[FP_REG_FIRST + 0]); |
| else |
| { |
| if (TARGET_BIG_ENDIAN) |
| { |
| fprintf (asm_out_file, "\tmfc1\t%s,%s\n", |
| reg_names[GP_REG_FIRST + 2], |
| reg_names[FP_REG_FIRST + 1]); |
| fprintf (asm_out_file, "\tmfc1\t%s,%s\n", |
| reg_names[GP_REG_FIRST + 3], |
| reg_names[FP_REG_FIRST + 0]); |
| } |
| else |
| { |
| fprintf (asm_out_file, "\tmfc1\t%s,%s\n", |
| reg_names[GP_REG_FIRST + 2], |
| reg_names[FP_REG_FIRST + 0]); |
| fprintf (asm_out_file, "\tmfc1\t%s,%s\n", |
| reg_names[GP_REG_FIRST + 3], |
| reg_names[FP_REG_FIRST + 1]); |
| } |
| } |
| fprintf (asm_out_file, "\tj\t%s\n", reg_names[GP_REG_FIRST + 18]); |
| /* As above, we can't fill the delay slot. */ |
| fprintf (asm_out_file, "\tnop\n"); |
| } |
| |
| fprintf (asm_out_file, "\t.set\treorder\n"); |
| |
| #ifdef ASM_DECLARE_FUNCTION_SIZE |
| ASM_DECLARE_FUNCTION_SIZE (asm_out_file, stubname, stubdecl); |
| #endif |
| |
| if (!FUNCTION_NAME_ALREADY_DECLARED) |
| { |
| fputs ("\t.end\t", asm_out_file); |
| assemble_name (asm_out_file, stubname); |
| fputs ("\n", asm_out_file); |
| } |
| |
| fprintf (asm_out_file, "\t.set\tmips16\n"); |
| |
| /* Record this stub. */ |
| l = (struct mips16_stub *) xmalloc (sizeof *l); |
| l->name = xstrdup (fnname); |
| l->fpret = fpret; |
| l->next = mips16_stubs; |
| mips16_stubs = l; |
| } |
| |
| /* If we expect a floating point return value, but we've built a |
| stub which does not expect one, then we're in trouble. We can't |
| use the existing stub, because it won't handle the floating point |
| value. We can't build a new stub, because the linker won't know |
| which stub to use for the various calls in this object file. |
| Fortunately, this case is illegal, since it means that a function |
| was declared in two different ways in a single compilation. */ |
| if (fpret && ! l->fpret) |
| error ("cannot handle inconsistent calls to %qs", fnname); |
| |
| /* If we are calling a stub which handles a floating point return |
| value, we need to arrange to save $18 in the prologue. We do |
| this by marking the function call as using the register. The |
| prologue will later see that it is used, and emit code to save |
| it. */ |
| |
| if (l->fpret) |
| { |
| rtx insn; |
| |
| if (retval == NULL_RTX) |
| insn = gen_call_internal (fn, arg_size); |
| else |
| insn = gen_call_value_internal (retval, fn, arg_size); |
| insn = emit_call_insn (insn); |
| |
| CALL_INSN_FUNCTION_USAGE (insn) = |
| gen_rtx_EXPR_LIST (VOIDmode, |
| gen_rtx_USE (VOIDmode, gen_rtx_REG (word_mode, 18)), |
| CALL_INSN_FUNCTION_USAGE (insn)); |
| |
| /* Return 1 to tell the caller that we've generated the call |
| insn. */ |
| return 1; |
| } |
| |
| /* Return 0 to let the caller generate the call insn. */ |
| return 0; |
| } |
| |
| /* An entry in the mips16 constant pool. VALUE is the pool constant, |
| MODE is its mode, and LABEL is the CODE_LABEL associated with it. */ |
| |
| struct mips16_constant { |
| struct mips16_constant *next; |
| rtx value; |
| rtx label; |
| enum machine_mode mode; |
| }; |
| |
| /* Information about an incomplete mips16 constant pool. FIRST is the |
| first constant, HIGHEST_ADDRESS is the highest address that the first |
| byte of the pool can have, and INSN_ADDRESS is the current instruction |
| address. */ |
| |
| struct mips16_constant_pool { |
| struct mips16_constant *first; |
| int highest_address; |
| int insn_address; |
| }; |
| |
| /* Add constant VALUE to POOL and return its label. MODE is the |
| value's mode (used for CONST_INTs, etc.). */ |
| |
| static rtx |
| add_constant (struct mips16_constant_pool *pool, |
| rtx value, enum machine_mode mode) |
| { |
| struct mips16_constant **p, *c; |
| bool first_of_size_p; |
| |
| /* See whether the constant is already in the pool. If so, return the |
| existing label, otherwise leave P pointing to the place where the |
| constant should be added. |
| |
| Keep the pool sorted in increasing order of mode size so that we can |
| reduce the number of alignments needed. */ |
| first_of_size_p = true; |
| for (p = &pool->first; *p != 0; p = &(*p)->next) |
| { |
| if (mode == (*p)->mode && rtx_equal_p (value, (*p)->value)) |
| return (*p)->label; |
| if (GET_MODE_SIZE (mode) < GET_MODE_SIZE ((*p)->mode)) |
| break; |
| if (GET_MODE_SIZE (mode) == GET_MODE_SIZE ((*p)->mode)) |
| first_of_size_p = false; |
| } |
| |
| /* In the worst case, the constant needed by the earliest instruction |
| will end up at the end of the pool. The entire pool must then be |
| accessible from that instruction. |
| |
| When adding the first constant, set the pool's highest address to |
| the address of the first out-of-range byte. Adjust this address |
| downwards each time a new constant is added. */ |
| if (pool->first == 0) |
| /* For pc-relative lw, addiu and daddiu instructions, the base PC value |
| is the address of the instruction with the lowest two bits clear. |
| The base PC value for ld has the lowest three bits clear. Assume |
| the worst case here. */ |
| pool->highest_address = pool->insn_address - (UNITS_PER_WORD - 2) + 0x8000; |
| pool->highest_address -= GET_MODE_SIZE (mode); |
| if (first_of_size_p) |
| /* Take into account the worst possible padding due to alignment. */ |
| pool->highest_address -= GET_MODE_SIZE (mode) - 1; |
| |
| /* Create a new entry. */ |
| c = (struct mips16_constant *) xmalloc (sizeof *c); |
| c->value = value; |
| c->mode = mode; |
| c->label = gen_label_rtx (); |
| c->next = *p; |
| *p = c; |
| |
| return c->label; |
| } |
| |
| /* Output constant VALUE after instruction INSN and return the last |
| instruction emitted. MODE is the mode of the constant. */ |
| |
| static rtx |
| dump_constants_1 (enum machine_mode mode, rtx value, rtx insn) |
| { |
| switch (GET_MODE_CLASS (mode)) |
| { |
| case MODE_INT: |
| { |
| rtx size = GEN_INT (GET_MODE_SIZE (mode)); |
| return emit_insn_after (gen_consttable_int (value, size), insn); |
| } |
| |
| case MODE_FLOAT: |
| return emit_insn_after (gen_consttable_float (value), insn); |
| |
| case MODE_VECTOR_FLOAT: |
| case MODE_VECTOR_INT: |
| { |
| int i; |
| for (i = 0; i < CONST_VECTOR_NUNITS (value); i++) |
| insn = dump_constants_1 (GET_MODE_INNER (mode), |
| CONST_VECTOR_ELT (value, i), insn); |
| return insn; |
| } |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| |
| /* Dump out the constants in CONSTANTS after INSN. */ |
| |
| static void |
| dump_constants (struct mips16_constant *constants, rtx insn) |
| { |
| struct mips16_constant *c, *next; |
| int align; |
| |
| align = 0; |
| for (c = constants; c != NULL; c = next) |
| { |
| /* If necessary, increase the alignment of PC. */ |
| if (align < GET_MODE_SIZE (c->mode)) |
| { |
| int align_log = floor_log2 (GET_MODE_SIZE (c->mode)); |
| insn = emit_insn_after (gen_align (GEN_INT (align_log)), insn); |
| } |
| align = GET_MODE_SIZE (c->mode); |
| |
| insn = emit_label_after (c->label, insn); |
| insn = dump_constants_1 (c->mode, c->value, insn); |
| |
| next = c->next; |
| free (c); |
| } |
| |
| emit_barrier_after (insn); |
| } |
| |
| /* Return the length of instruction INSN. */ |
| |
| static int |
| mips16_insn_length (rtx insn) |
| { |
| if (JUMP_P (insn)) |
| { |
| rtx body = PATTERN (insn); |
| if (GET_CODE (body) == ADDR_VEC) |
| return GET_MODE_SIZE (GET_MODE (body)) * XVECLEN (body, 0); |
| if (GET_CODE (body) == ADDR_DIFF_VEC) |
| return GET_MODE_SIZE (GET_MODE (body)) * XVECLEN (body, 1); |
| } |
| return get_attr_length (insn); |
| } |
| |
| /* Rewrite *X so that constant pool references refer to the constant's |
| label instead. DATA points to the constant pool structure. */ |
| |
| static int |
| mips16_rewrite_pool_refs (rtx *x, void *data) |
| { |
| struct mips16_constant_pool *pool = data; |
| if (GET_CODE (*x) == SYMBOL_REF && CONSTANT_POOL_ADDRESS_P (*x)) |
| *x = gen_rtx_LABEL_REF (Pmode, add_constant (pool, |
| get_pool_constant (*x), |
| get_pool_mode (*x))); |
| return 0; |
| } |
| |
| /* Build MIPS16 constant pools. */ |
| |
| static void |
| mips16_lay_out_constants (void) |
| { |
| struct mips16_constant_pool pool; |
| rtx insn, barrier; |
| |
| barrier = 0; |
| memset (&pool, 0, sizeof (pool)); |
| for (insn = get_insns (); insn; insn = NEXT_INSN (insn)) |
| { |
| /* Rewrite constant pool references in INSN. */ |
| if (INSN_P (insn)) |
| for_each_rtx (&PATTERN (insn), mips16_rewrite_pool_refs, &pool); |
| |
| pool.insn_address += mips16_insn_length (insn); |
| |
| if (pool.first != NULL) |
| { |
| /* If there are no natural barriers between the first user of |
| the pool and the highest acceptable address, we'll need to |
| create a new instruction to jump around the constant pool. |
| In the worst case, this instruction will be 4 bytes long. |
| |
| If it's too late to do this transformation after INSN, |
| do it immediately before INSN. */ |
| if (barrier == 0 && pool.insn_address + 4 > pool.highest_address) |
| { |
| rtx label, jump; |
| |
| label = gen_label_rtx (); |
| |
| jump = emit_jump_insn_before (gen_jump (label), insn); |
| JUMP_LABEL (jump) = label; |
| LABEL_NUSES (label) = 1; |
| barrier = emit_barrier_after (jump); |
| |
| emit_label_after (label, barrier); |
| pool.insn_address += 4; |
| } |
| |
| /* See whether the constant pool is now out of range of the first |
| user. If so, output the constants after the previous barrier. |
| Note that any instructions between BARRIER and INSN (inclusive) |
| will use negative offsets to refer to the pool. */ |
| if (pool.insn_address > pool.highest_address) |
| { |
| dump_constants (pool.first, barrier); |
| pool.first = NULL; |
| barrier = 0; |
| } |
| else if (BARRIER_P (insn)) |
| barrier = insn; |
| } |
| } |
| dump_constants (pool.first, get_last_insn ()); |
| } |
| |
| /* A temporary variable used by for_each_rtx callbacks, etc. */ |
| static rtx mips_sim_insn; |
| |
| /* A structure representing the state of the processor pipeline. |
| Used by the mips_sim_* family of functions. */ |
| struct mips_sim { |
| /* The maximum number of instructions that can be issued in a cycle. |
| (Caches mips_issue_rate.) */ |
| unsigned int issue_rate; |
| |
| /* The current simulation time. */ |
| unsigned int time; |
| |
| /* How many more instructions can be issued in the current cycle. */ |
| unsigned int insns_left; |
| |
| /* LAST_SET[X].INSN is the last instruction to set register X. |
| LAST_SET[X].TIME is the time at which that instruction was issued. |
| INSN is null if no instruction has yet set register X. */ |
| struct { |
| rtx insn; |
| unsigned int time; |
| } last_set[FIRST_PSEUDO_REGISTER]; |
| |
| /* The pipeline's current DFA state. */ |
| state_t dfa_state; |
| }; |
| |
| /* Reset STATE to the initial simulation state. */ |
| |
| static void |
| mips_sim_reset (struct mips_sim *state) |
| { |
| state->time = 0; |
| state->insns_left = state->issue_rate; |
| memset (&state->last_set, 0, sizeof (state->last_set)); |
| state_reset (state->dfa_state); |
| } |
| |
| /* Initialize STATE before its first use. DFA_STATE points to an |
| allocated but uninitialized DFA state. */ |
| |
| static void |
| mips_sim_init (struct mips_sim *state, state_t dfa_state) |
| { |
| state->issue_rate = mips_issue_rate (); |
| state->dfa_state = dfa_state; |
| mips_sim_reset (state); |
| } |
| |
| /* Advance STATE by one clock cycle. */ |
| |
| static void |
| mips_sim_next_cycle (struct mips_sim *state) |
| { |
| state->time++; |
| state->insns_left = state->issue_rate; |
| state_transition (state->dfa_state, 0); |
| } |
| |
| /* Advance simulation state STATE until instruction INSN can read |
| register REG. */ |
| |
| static void |
| mips_sim_wait_reg (struct mips_sim *state, rtx insn, rtx reg) |
| { |
| unsigned int i; |
| |
| for (i = 0; i < HARD_REGNO_NREGS (REGNO (reg), GET_MODE (reg)); i++) |
| if (state->last_set[REGNO (reg) + i].insn != 0) |
| { |
| unsigned int t; |
| |
| t = state->last_set[REGNO (reg) + i].time; |
| t += insn_latency (state->last_set[REGNO (reg) + i].insn, insn); |
| while (state->time < t) |
| mips_sim_next_cycle (state); |
| } |
| } |
| |
| /* A for_each_rtx callback. If *X is a register, advance simulation state |
| DATA until mips_sim_insn can read the register's value. */ |
| |
| static int |
| mips_sim_wait_regs_2 (rtx *x, void *data) |
| { |
| if (REG_P (*x)) |
| mips_sim_wait_reg (data, mips_sim_insn, *x); |
| return 0; |
| } |
| |
| /* Call mips_sim_wait_regs_2 (R, DATA) for each register R mentioned in *X. */ |
| |
| static void |
| mips_sim_wait_regs_1 (rtx *x, void *data) |
| { |
| for_each_rtx (x, mips_sim_wait_regs_2, data); |
| } |
| |
| /* Advance simulation state STATE until all of INSN's register |
| dependencies are satisfied. */ |
| |
| static void |
| mips_sim_wait_regs (struct mips_sim *state, rtx insn) |
| { |
| mips_sim_insn = insn; |
| note_uses (&PATTERN (insn), mips_sim_wait_regs_1, state); |
| } |
| |
| /* Advance simulation state STATE until the units required by |
| instruction INSN are available. */ |
| |
| static void |
| mips_sim_wait_units (struct mips_sim *state, rtx insn) |
| { |
| state_t tmp_state; |
| |
| tmp_state = alloca (state_size ()); |
| while (state->insns_left == 0 |
| || (memcpy (tmp_state, state->dfa_state, state_size ()), |
| state_transition (tmp_state, insn) >= 0)) |
| mips_sim_next_cycle (state); |
| } |
| |
| /* Advance simulation state STATE until INSN is ready to issue. */ |
| |
| static void |
| mips_sim_wait_insn (struct mips_sim *state, rtx insn) |
| { |
| mips_sim_wait_regs (state, insn); |
| mips_sim_wait_units (state, insn); |
| } |
| |
| /* mips_sim_insn has just set X. Update the LAST_SET array |
| in simulation state DATA. */ |
| |
| static void |
| mips_sim_record_set (rtx x, rtx pat ATTRIBUTE_UNUSED, void *data) |
| { |
| struct mips_sim *state; |
| unsigned int i; |
| |
| state = data; |
| if (REG_P (x)) |
| for (i = 0; i < HARD_REGNO_NREGS (REGNO (x), GET_MODE (x)); i++) |
| { |
| state->last_set[REGNO (x) + i].insn = mips_sim_insn; |
| state->last_set[REGNO (x) + i].time = state->time; |
| } |
| } |
| |
| /* Issue instruction INSN in scheduler state STATE. Assume that INSN |
| can issue immediately (i.e., that mips_sim_wait_insn has already |
| been called). */ |
| |
| static void |
| mips_sim_issue_insn (struct mips_sim *state, rtx insn) |
| { |
| state_transition (state->dfa_state, insn); |
| state->insns_left--; |
| |
| mips_sim_insn = insn; |
| note_stores (PATTERN (insn), mips_sim_record_set, state); |
| } |
| |
| /* Simulate issuing a NOP in state STATE. */ |
| |
| static void |
| mips_sim_issue_nop (struct mips_sim *state) |
| { |
| if (state->insns_left == 0) |
| mips_sim_next_cycle (state); |
| state->insns_left--; |
| } |
| |
| /* Update simulation state STATE so that it's ready to accept the instruction |
| after INSN. INSN should be part of the main rtl chain, not a member of a |
| SEQUENCE. */ |
| |
| static void |
| mips_sim_finish_insn (struct mips_sim *state, rtx insn) |
| { |
| /* If INSN is a jump with an implicit delay slot, simulate a nop. */ |
| if (JUMP_P (insn)) |
| mips_sim_issue_nop (state); |
| |
| switch (GET_CODE (SEQ_BEGIN (insn))) |
| { |
| case CODE_LABEL: |
| case CALL_INSN: |
| /* We can't predict the processor state after a call or label. */ |
| mips_sim_reset (state); |
| break; |
| |
| case JUMP_INSN: |
| /* The delay slots of branch likely instructions are only executed |
| when the branch is taken. Therefore, if the caller has simulated |
| the delay slot instruction, STATE does not really reflect the state |
| of the pipeline for the instruction after the delay slot. Also, |
| branch likely instructions tend to incur a penalty when not taken, |
| so there will probably be an extra delay between the branch and |
| the instruction after the delay slot. */ |
| if (INSN_ANNULLED_BRANCH_P (SEQ_BEGIN (insn))) |
| mips_sim_reset (state); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| /* The VR4130 pipeline issues aligned pairs of instructions together, |
| but it stalls the second instruction if it depends on the first. |
| In order to cut down the amount of logic required, this dependence |
| check is not based on a full instruction decode. Instead, any non-SPECIAL |
| instruction is assumed to modify the register specified by bits 20-16 |
| (which is usually the "rt" field). |
| |
| In beq, beql, bne and bnel instructions, the rt field is actually an |
| input, so we can end up with a false dependence between the branch |
| and its delay slot. If this situation occurs in instruction INSN, |
| try to avoid it by swapping rs and rt. */ |
| |
| static void |
| vr4130_avoid_branch_rt_conflict (rtx insn) |
| { |
| rtx first, second; |
| |
| first = SEQ_BEGIN (insn); |
| second = SEQ_END (insn); |
| if (JUMP_P (first) |
| && NONJUMP_INSN_P (second) |
| && GET_CODE (PATTERN (first)) == SET |
| && GET_CODE (SET_DEST (PATTERN (first))) == PC |
| && GET_CODE (SET_SRC (PATTERN (first))) == IF_THEN_ELSE) |
| { |
| /* Check for the right kind of condition. */ |
| rtx cond = XEXP (SET_SRC (PATTERN (first)), 0); |
| if ((GET_CODE (cond) == EQ || GET_CODE (cond) == NE) |
| && REG_P (XEXP (cond, 0)) |
| && REG_P (XEXP (cond, 1)) |
| && reg_referenced_p (XEXP (cond, 1), PATTERN (second)) |
| && !reg_referenced_p (XEXP (cond, 0), PATTERN (second))) |
| { |
| /* SECOND mentions the rt register but not the rs register. */ |
| rtx tmp = XEXP (cond, 0); |
| XEXP (cond, 0) = XEXP (cond, 1); |
| XEXP (cond, 1) = tmp; |
| } |
| } |
| } |
| |
| /* Implement -mvr4130-align. Go through each basic block and simulate the |
| processor pipeline. If we find that a pair of instructions could execute |
| in parallel, and the first of those instruction is not 8-byte aligned, |
| insert a nop to make it aligned. */ |
| |
| static void |
| vr4130_align_insns (void) |
| { |
| struct mips_sim state; |
| rtx insn, subinsn, last, last2, next; |
| bool aligned_p; |
| |
| dfa_start (); |
| |
| /* LAST is the last instruction before INSN to have a nonzero length. |
| LAST2 is the last such instruction before LAST. */ |
| last = 0; |
| last2 = 0; |
| |
| /* ALIGNED_P is true if INSN is known to be at an aligned address. */ |
| aligned_p = true; |
| |
| mips_sim_init (&state, alloca (state_size ())); |
| for (insn = get_insns (); insn != 0; insn = next) |
| { |
| unsigned int length; |
| |
| next = NEXT_INSN (insn); |
| |
| /* See the comment above vr4130_avoid_branch_rt_conflict for details. |
| This isn't really related to the alignment pass, but we do it on |
| the fly to avoid a separate instruction walk. */ |
| vr4130_avoid_branch_rt_conflict (insn); |
| |
| if (USEFUL_INSN_P (insn)) |
| FOR_EACH_SUBINSN (subinsn, insn) |
| { |
| mips_sim_wait_insn (&state, subinsn); |
| |
| /* If we want this instruction to issue in parallel with the |
| previous one, make sure that the previous instruction is |
| aligned. There are several reasons why this isn't worthwhile |
| when the second instruction is a call: |
| |
| - Calls are less likely to be performance critical, |
| - There's a good chance that the delay slot can execute |
| in parallel with the call. |
| - The return address would then be unaligned. |
| |
| In general, if we're going to insert a nop between instructions |
| X and Y, it's better to insert it immediately after X. That |
| way, if the nop makes Y aligned, it will also align any labels |
| between X and Y. */ |
| if (state.insns_left != state.issue_rate |
| && !CALL_P (subinsn)) |
| { |
| if (subinsn == SEQ_BEGIN (insn) && aligned_p) |
| { |
| /* SUBINSN is the first instruction in INSN and INSN is |
| aligned. We want to align the previous instruction |
| instead, so insert a nop between LAST2 and LAST. |
| |
| Note that LAST could be either a single instruction |
| or a branch with a delay slot. In the latter case, |
| LAST, like INSN, is already aligned, but the delay |
| slot must have some extra delay that stops it from |
| issuing at the same time as the branch. We therefore |
| insert a nop before the branch in order to align its |
| delay slot. */ |
| emit_insn_after (gen_nop (), last2); |
| aligned_p = false; |
| } |
| else if (subinsn != SEQ_BEGIN (insn) && !aligned_p) |
| { |
| /* SUBINSN is the delay slot of INSN, but INSN is |
| currently unaligned. Insert a nop between |
| LAST and INSN to align it. */ |
| emit_insn_after (gen_nop (), last); |
| aligned_p = true; |
| } |
| } |
| mips_sim_issue_insn (&state, subinsn); |
| } |
| mips_sim_finish_insn (&state, insn); |
| |
| /* Update LAST, LAST2 and ALIGNED_P for the next instruction. */ |
| length = get_attr_length (insn); |
| if (length > 0) |
| { |
| /* If the instruction is an asm statement or multi-instruction |
| mips.md patern, the length is only an estimate. Insert an |
| 8 byte alignment after it so that the following instructions |
| can be handled correctly. */ |
| if (NONJUMP_INSN_P (SEQ_BEGIN (insn)) |
| && (recog_memoized (insn) < 0 || length >= 8)) |
| { |
| next = emit_insn_after (gen_align (GEN_INT (3)), insn); |
| next = NEXT_INSN (next); |
| mips_sim_next_cycle (&state); |
| aligned_p = true; |
| } |
| else if (length & 4) |
| aligned_p = !aligned_p; |
| last2 = last; |
| last = insn; |
| } |
| |
| /* See whether INSN is an aligned label. */ |
| if (LABEL_P (insn) && label_to_alignment (insn) >= 3) |
| aligned_p = true; |
| } |
| dfa_finish (); |
| } |
| |
| /* Subroutine of mips_reorg. If there is a hazard between INSN |
| and a previous instruction, avoid it by inserting nops after |
| instruction AFTER. |
| |
| *DELAYED_REG and *HILO_DELAY describe the hazards that apply at |
| this point. If *DELAYED_REG is non-null, INSN must wait a cycle |
| before using the value of that register. *HILO_DELAY counts the |
| number of instructions since the last hilo hazard (that is, |
| the number of instructions since the last mflo or mfhi). |
| |
| After inserting nops for INSN, update *DELAYED_REG and *HILO_DELAY |
| for the next instruction. |
| |
| LO_REG is an rtx for the LO register, used in dependence checking. */ |
| |
| static void |
| mips_avoid_hazard (rtx after, rtx insn, int *hilo_delay, |
| rtx *delayed_reg, rtx lo_reg) |
| { |
| rtx pattern, set; |
| int nops, ninsns; |
| |
| if (!INSN_P (insn)) |
| return; |
| |
| pattern = PATTERN (insn); |
| |
| /* Do not put the whole function in .set noreorder if it contains |
| an asm statement. We don't know whether there will be hazards |
| between the asm statement and the gcc-generated code. */ |
| if (GET_CODE (pattern) == ASM_INPUT || asm_noperands (pattern) >= 0) |
| cfun->machine->all_noreorder_p = false; |
| |
| /* Ignore zero-length instructions (barriers and the like). */ |
| ninsns = get_attr_length (insn) / 4; |
| if (ninsns == 0) |
| return; |
| |
| /* Work out how many nops are needed. Note that we only care about |
| registers that are explicitly mentioned in the instruction's pattern. |
| It doesn't matter that calls use the argument registers or that they |
| clobber hi and lo. */ |
| if (*hilo_delay < 2 && reg_set_p (lo_reg, pattern)) |
| nops = 2 - *hilo_delay; |
| else if (*delayed_reg != 0 && reg_referenced_p (*delayed_reg, pattern)) |
| nops = 1; |
| else |
| nops = 0; |
| |
| /* Insert the nops between this instruction and the previous one. |
| Each new nop takes us further from the last hilo hazard. */ |
| *hilo_delay += nops; |
| while (nops-- > 0) |
| emit_insn_after (gen_hazard_nop (), after); |
| |
| /* Set up the state for the next instruction. */ |
| *hilo_delay += ninsns; |
| *delayed_reg = 0; |
| if (INSN_CODE (insn) >= 0) |
| switch (get_attr_hazard (insn)) |
| { |
| case HAZARD_NONE: |
| break; |
| |
| case HAZARD_HILO: |
| *hilo_delay = 0; |
| break; |
| |
| case HAZARD_DELAY: |
| set = single_set (insn); |
| gcc_assert (set != 0); |
| *delayed_reg = SET_DEST (set); |
| break; |
| } |
| } |
| |
| |
| /* Go through the instruction stream and insert nops where necessary. |
| See if the whole function can then be put into .set noreorder & |
| .set nomacro. */ |
| |
| static void |
| mips_avoid_hazards (void) |
| { |
| rtx insn, last_insn, lo_reg, delayed_reg; |
| int hilo_delay, i; |
| |
| /* Force all instructions to be split into their final form. */ |
| split_all_insns_noflow (); |
| |
| /* Recalculate instruction lengths without taking nops into account. */ |
| cfun->machine->ignore_hazard_length_p = true; |
| shorten_branches (get_insns ()); |
| |
| cfun->machine->all_noreorder_p = true; |
| |
| /* Profiled functions can't be all noreorder because the profiler |
| support uses assembler macros. */ |
| if (current_function_profile) |
| cfun->machine->all_noreorder_p = false; |
| |
| /* Code compiled with -mfix-vr4120 can't be all noreorder because |
| we rely on the assembler to work around some errata. */ |
| if (TARGET_FIX_VR4120) |
| cfun->machine->all_noreorder_p = false; |
| |
| /* The same is true for -mfix-vr4130 if we might generate mflo or |
| mfhi instructions. Note that we avoid using mflo and mfhi if |
| the VR4130 macc and dmacc instructions are available instead; |
| see the *mfhilo_{si,di}_macc patterns. */ |
| if (TARGET_FIX_VR4130 && !ISA_HAS_MACCHI) |
| cfun->machine->all_noreorder_p = false; |
| |
| last_insn = 0; |
| hilo_delay = 2; |
| delayed_reg = 0; |
| lo_reg = gen_rtx_REG (SImode, LO_REGNUM); |
| |
| for (insn = get_insns (); insn != 0; insn = NEXT_INSN (insn)) |
| if (INSN_P (insn)) |
| { |
| if (GET_CODE (PATTERN (insn)) == SEQUENCE) |
| for (i = 0; i < XVECLEN (PATTERN (insn), 0); i++) |
| mips_avoid_hazard (last_insn, XVECEXP (PATTERN (insn), 0, i), |
| &hilo_delay, &delayed_reg, lo_reg); |
| else |
| mips_avoid_hazard (last_insn, insn, &hilo_delay, |
| &delayed_reg, lo_reg); |
| |
| last_insn = insn; |
| } |
| } |
| |
| |
| /* Implement TARGET_MACHINE_DEPENDENT_REORG. */ |
| |
| static void |
| mips_reorg (void) |
| { |
| if (TARGET_MIPS16) |
| mips16_lay_out_constants (); |
| else if (TARGET_EXPLICIT_RELOCS) |
| { |
| if (mips_flag_delayed_branch) |
| dbr_schedule (get_insns (), dump_file); |
| mips_avoid_hazards (); |
| if (TUNE_MIPS4130 && TARGET_VR4130_ALIGN) |
| vr4130_align_insns (); |
| } |
| } |
| |
| /* This function does three things: |
| |
| - Register the special divsi3 and modsi3 functions if -mfix-vr4120. |
| - Register the mips16 hardware floating point stubs. |
| - Register the gofast functions if selected using --enable-gofast. */ |
| |
| #include "config/gofast.h" |
| |
| static void |
| mips_init_libfuncs (void) |
| { |
| if (TARGET_FIX_VR4120) |
| { |
| set_optab_libfunc (sdiv_optab, SImode, "__vr4120_divsi3"); |
| set_optab_libfunc (smod_optab, SImode, "__vr4120_modsi3"); |
| } |
| |
| if (TARGET_MIPS16 && mips16_hard_float) |
| { |
| set_optab_libfunc (add_optab, SFmode, "__mips16_addsf3"); |
| set_optab_libfunc (sub_optab, SFmode, "__mips16_subsf3"); |
| set_optab_libfunc (smul_optab, SFmode, "__mips16_mulsf3"); |
| set_optab_libfunc (sdiv_optab, SFmode, "__mips16_divsf3"); |
| |
| set_optab_libfunc (eq_optab, SFmode, "__mips16_eqsf2"); |
| set_optab_libfunc (ne_optab, SFmode, "__mips16_nesf2"); |
| set_optab_libfunc (gt_optab, SFmode, "__mips16_gtsf2"); |
| set_optab_libfunc (ge_optab, SFmode, "__mips16_gesf2"); |
| set_optab_libfunc (lt_optab, SFmode, "__mips16_ltsf2"); |
| set_optab_libfunc (le_optab, SFmode, "__mips16_lesf2"); |
| |
| set_conv_libfunc (sfix_optab, SImode, SFmode, "__mips16_fix_truncsfsi"); |
| set_conv_libfunc (sfloat_optab, SFmode, SImode, "__mips16_floatsisf"); |
| |
| if (TARGET_DOUBLE_FLOAT) |
| { |
| set_optab_libfunc (add_optab, DFmode, "__mips16_adddf3"); |
| set_optab_libfunc (sub_optab, DFmode, "__mips16_subdf3"); |
| set_optab_libfunc (smul_optab, DFmode, "__mips16_muldf3"); |
| set_optab_libfunc (sdiv_optab, DFmode, "__mips16_divdf3"); |
| |
| set_optab_libfunc (eq_optab, DFmode, "__mips16_eqdf2"); |
| set_optab_libfunc (ne_optab, DFmode, "__mips16_nedf2"); |
| set_optab_libfunc (gt_optab, DFmode, "__mips16_gtdf2"); |
| set_optab_libfunc (ge_optab, DFmode, "__mips16_gedf2"); |
| set_optab_libfunc (lt_optab, DFmode, "__mips16_ltdf2"); |
| set_optab_libfunc (le_optab, DFmode, "__mips16_ledf2"); |
| |
| set_conv_libfunc (sext_optab, DFmode, SFmode, "__mips16_extendsfdf2"); |
| set_conv_libfunc (trunc_optab, SFmode, DFmode, "__mips16_truncdfsf2"); |
| |
| set_conv_libfunc (sfix_optab, SImode, DFmode, "__mips16_fix_truncdfsi"); |
| set_conv_libfunc (sfloat_optab, DFmode, SImode, "__mips16_floatsidf"); |
| } |
| } |
| else |
| gofast_maybe_init_libfuncs (); |
| } |
| |
| /* Return a number assessing the cost of moving a register in class |
| FROM to class TO. The classes are expressed using the enumeration |
| values such as `GENERAL_REGS'. A value of 2 is the default; other |
| values are interpreted relative to that. |
| |
| It is not required that the cost always equal 2 when FROM is the |
| same as TO; on some machines it is expensive to move between |
| registers if they are not general registers. |
| |
| If reload sees an insn consisting of a single `set' between two |
| hard registers, and if `REGISTER_MOVE_COST' applied to their |
| classes returns a value of 2, reload does not check to ensure that |
| the constraints of the insn are met. Setting a cost of other than |
| 2 will allow reload to verify that the constraints are met. You |
| should do this if the `movM' pattern's constraints do not allow |
| such copying. |
| |
| ??? We make the cost of moving from HI/LO into general |
| registers the same as for one of moving general registers to |
| HI/LO for TARGET_MIPS16 in order to prevent allocating a |
| pseudo to HI/LO. This might hurt optimizations though, it |
| isn't clear if it is wise. And it might not work in all cases. We |
| could solve the DImode LO reg problem by using a multiply, just |
| like reload_{in,out}si. We could solve the SImode/HImode HI reg |
| problem by using divide instructions. divu puts the remainder in |
| the HI reg, so doing a divide by -1 will move the value in the HI |
| reg for all values except -1. We could handle that case by using a |
| signed divide, e.g. -1 / 2 (or maybe 1 / -2?). We'd have to emit |
| a compare/branch to test the input value to see which instruction |
| we need to use. This gets pretty messy, but it is feasible. */ |
| |
| int |
| mips_register_move_cost (enum machine_mode mode ATTRIBUTE_UNUSED, |
| enum reg_class to, enum reg_class from) |
| { |
| if (from == M16_REGS && GR_REG_CLASS_P (to)) |
| return 2; |
| else if (from == M16_NA_REGS && GR_REG_CLASS_P (to)) |
| return 2; |
| else if (GR_REG_CLASS_P (from)) |
| { |
| if (to == M16_REGS) |
| return 2; |
| else if (to == M16_NA_REGS) |
| return 2; |
| else if (GR_REG_CLASS_P (to)) |
| { |
| if (TARGET_MIPS16) |
| return 4; |
| else |
| return 2; |
| } |
| else if (to == FP_REGS) |
| return 4; |
| else if (to == HI_REG || to == LO_REG || to == MD_REGS) |
| { |
| if (TARGET_MIPS16) |
| return 12; |
| else |
| return 6; |
| } |
| else if (COP_REG_CLASS_P (to)) |
| { |
| return 5; |
| } |
| } /* GR_REG_CLASS_P (from) */ |
| else if (from == FP_REGS) |
| { |
| if (GR_REG_CLASS_P (to)) |
| return 4; |
| else if (to == FP_REGS) |
| return 2; |
| else if (to == ST_REGS) |
| return 8; |
| } /* from == FP_REGS */ |
| else if (from == HI_REG || from == LO_REG || from == MD_REGS) |
| { |
| if (GR_REG_CLASS_P (to)) |
| { |
| if (TARGET_MIPS16) |
| return 12; |
| else |
| return 6; |
| } |
| } /* from == HI_REG, etc. */ |
| else if (from == ST_REGS && GR_REG_CLASS_P (to)) |
| return 4; |
| else if (COP_REG_CLASS_P (from)) |
| { |
| return 5; |
| } /* COP_REG_CLASS_P (from) */ |
| |
| /* Fall through. */ |
| |
| return 12; |
| } |
| |
| /* Return the length of INSN. LENGTH is the initial length computed by |
| attributes in the machine-description file. */ |
| |
| int |
| mips_adjust_insn_length (rtx insn, int length) |
| { |
| /* A unconditional jump has an unfilled delay slot if it is not part |
| of a sequence. A conditional jump normally has a delay slot, but |
| does not on MIPS16. */ |
| if (CALL_P (insn) || (TARGET_MIPS16 ? simplejump_p (insn) : JUMP_P (insn))) |
| length += 4; |
| |
| /* See how many nops might be needed to avoid hardware hazards. */ |
| if (!cfun->machine->ignore_hazard_length_p && INSN_CODE (insn) >= 0) |
| switch (get_attr_hazard (insn)) |
| { |
| case HAZARD_NONE: |
| break; |
| |
| case HAZARD_DELAY: |
| length += 4; |
| break; |
| |
| case HAZARD_HILO: |
| length += 8; |
| break; |
| } |
| |
| /* All MIPS16 instructions are a measly two bytes. */ |
| if (TARGET_MIPS16) |
| length /= 2; |
| |
| return length; |
| } |
| |
| |
| /* Return an asm sequence to start a noat block and load the address |
| of a label into $1. */ |
| |
| const char * |
| mips_output_load_label (void) |
| { |
| if (TARGET_EXPLICIT_RELOCS) |
| switch (mips_abi) |
| { |
| case ABI_N32: |
| return "%[lw\t%@,%%got_page(%0)(%+)\n\taddiu\t%@,%@,%%got_ofst(%0)"; |
| |
| case ABI_64: |
| return "%[ld\t%@,%%got_page(%0)(%+)\n\tdaddiu\t%@,%@,%%got_ofst(%0)"; |
| |
| default: |
| if (ISA_HAS_LOAD_DELAY) |
| return "%[lw\t%@,%%got(%0)(%+)%#\n\taddiu\t%@,%@,%%lo(%0)"; |
| return "%[lw\t%@,%%got(%0)(%+)\n\taddiu\t%@,%@,%%lo(%0)"; |
| } |
| else |
| { |
| if (Pmode == DImode) |
| return "%[dla\t%@,%0"; |
| else |
| return "%[la\t%@,%0"; |
| } |
| } |
| |
| |
| /* Output assembly instructions to peform a conditional branch. |
| |
| INSN is the branch instruction. OPERANDS[0] is the condition. |
| OPERANDS[1] is the target of the branch. OPERANDS[2] is the target |
| of the first operand to the condition. If TWO_OPERANDS_P is |
| nonzero the comparison takes two operands; OPERANDS[3] will be the |
| second operand. |
| |
| If INVERTED_P is nonzero we are to branch if the condition does |
| not hold. If FLOAT_P is nonzero this is a floating-point comparison. |
| |
| LENGTH is the length (in bytes) of the sequence we are to generate. |
| That tells us whether to generate a simple conditional branch, or a |
| reversed conditional branch around a `jr' instruction. */ |
| const char * |
| mips_output_conditional_branch (rtx insn, rtx *operands, int two_operands_p, |
| int float_p, int inverted_p, int length) |
| { |
| static char buffer[200]; |
| /* The kind of comparison we are doing. */ |
| enum rtx_code code = GET_CODE (operands[0]); |
| /* Nonzero if the opcode for the comparison needs a `z' indicating |
| that it is a comparison against zero. */ |
| int need_z_p; |
| /* A string to use in the assembly output to represent the first |
| operand. */ |
| const char *op1 = "%z2"; |
| /* A string to use in the assembly output to represent the second |
| operand. Use the hard-wired zero register if there's no second |
| operand. */ |
| const char *op2 = (two_operands_p ? ",%z3" : ",%."); |
| /* The operand-printing string for the comparison. */ |
| const char *const comp = (float_p ? "%F0" : "%C0"); |
| /* The operand-printing string for the inverted comparison. */ |
| const char *const inverted_comp = (float_p ? "%W0" : "%N0"); |
| |
| /* The MIPS processors (for levels of the ISA at least two), have |
| "likely" variants of each branch instruction. These instructions |
| annul the instruction in the delay slot if the branch is not |
| taken. */ |
| mips_branch_likely = (final_sequence && INSN_ANNULLED_BRANCH_P (insn)); |
| |
| if (!two_operands_p) |
| { |
| /* To compute whether than A > B, for example, we normally |
| subtract B from A and then look at the sign bit. But, if we |
| are doing an unsigned comparison, and B is zero, we don't |
| have to do the subtraction. Instead, we can just check to |
| see if A is nonzero. Thus, we change the CODE here to |
| reflect the simpler comparison operation. */ |
| switch (code) |
| { |
| case GTU: |
| code = NE; |
| break; |
| |
| case LEU: |
| code = EQ; |
| break; |
| |
| case GEU: |
| /* A condition which will always be true. */ |
| code = EQ; |
| op1 = "%."; |
| break; |
| |
| case LTU: |
| /* A condition which will always be false. */ |
| code = NE; |
| op1 = "%."; |
| break; |
| |
| default: |
| /* Not a special case. */ |
| break; |
| } |
| } |
| |
| /* Relative comparisons are always done against zero. But |
| equality comparisons are done between two operands, and therefore |
| do not require a `z' in the assembly language output. */ |
| need_z_p = (!float_p && code != EQ && code != NE); |
| /* For comparisons against zero, the zero is not provided |
| explicitly. */ |
| if (need_z_p) |
| op2 = ""; |
| |
| /* Begin by terminating the buffer. That way we can always use |
| strcat to add to it. */ |
| buffer[0] = '\0'; |
| |
| switch (length) |
| { |
| case 4: |
| case 8: |
| /* Just a simple conditional branch. */ |
| if (float_p) |
| sprintf (buffer, "%%*b%s%%?\t%%Z2%%1%%/", |
| inverted_p ? inverted_comp : comp); |
| else |
| sprintf (buffer, "%%*b%s%s%%?\t%s%s,%%1%%/", |
| inverted_p ? inverted_comp : comp, |
| need_z_p ? "z" : "", |
| op1, |
| op2); |
| return buffer; |
| |
| case 12: |
| case 16: |
| case 24: |
| case 28: |
| { |
| /* Generate a reversed conditional branch around ` j' |
| instruction: |
| |
| .set noreorder |
| .set nomacro |
| bc l |
| delay_slot or #nop |
| j target |
| #nop |
| l: |
| .set macro |
| .set reorder |
| |
| If the original branch was a likely branch, the delay slot |
| must be executed only if the branch is taken, so generate: |
| |
| .set noreorder |
| .set nomacro |
| bc l |
| #nop |
| j target |
| delay slot or #nop |
| l: |
| .set macro |
| .set reorder |
| |
| When generating PIC, instead of: |
| |
| j target |
| |
| we emit: |
| |
| .set noat |
| la $at, target |
| jr $at |
| .set at |
| */ |
| |
| rtx orig_target; |
| rtx target = gen_label_rtx (); |
| |
| orig_target = operands[1]; |
| operands[1] = target; |
| /* Generate the reversed comparison. This takes four |
| bytes. */ |
| if (float_p) |
| sprintf (buffer, "%%*b%s\t%%Z2%%1", |
| inverted_p ? comp : inverted_comp); |
| else |
| sprintf (buffer, "%%*b%s%s\t%s%s,%%1", |
| inverted_p ? comp : inverted_comp, |
| need_z_p ? "z" : "", |
| op1, |
| op2); |
| output_asm_insn (buffer, operands); |
| |
| if (length != 16 && length != 28 && ! mips_branch_likely) |
| { |
| /* Output delay slot instruction. */ |
| rtx insn = final_sequence; |
| final_scan_insn (XVECEXP (insn, 0, 1), asm_out_file, |
| optimize, 0, 1, NULL); |
| INSN_DELETED_P (XVECEXP (insn, 0, 1)) = 1; |
| } |
| else |
| output_asm_insn ("%#", 0); |
| |
| if (length <= 16) |
| output_asm_insn ("j\t%0", &orig_target); |
| else |
| { |
| output_asm_insn (mips_output_load_label (), &orig_target); |
| output_asm_insn ("jr\t%@%]", 0); |
| } |
| |
| if (length != 16 && length != 28 && mips_branch_likely) |
| { |
| /* Output delay slot instruction. */ |
| rtx insn = final_sequence; |
| final_scan_insn (XVECEXP (insn, 0, 1), asm_out_file, |
| optimize, 0, 1, NULL); |
| INSN_DELETED_P (XVECEXP (insn, 0, 1)) = 1; |
| } |
| else |
| output_asm_insn ("%#", 0); |
| |
| (*targetm.asm_out.internal_label) (asm_out_file, "L", |
| CODE_LABEL_NUMBER (target)); |
| |
| return ""; |
| } |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| /* NOTREACHED */ |
| return 0; |
| } |
| |
| /* Used to output div or ddiv instruction DIVISION, which has the operands |
| given by OPERANDS. Add in a divide-by-zero check if needed. |
| |
| When working around R4000 and R4400 errata, we need to make sure that |
| the division is not immediately followed by a shift[1][2]. We also |
| need to stop the division from being put into a branch delay slot[3]. |
| The easiest way to avoid both problems is to add a nop after the |
| division. When a divide-by-zero check is needed, this nop can be |
| used to fill the branch delay slot. |
| |
| [1] If a double-word or a variable shift executes immediately |
| after starting an integer division, the shift may give an |
| incorrect result. See quotations of errata #16 and #28 from |
| "MIPS R4000PC/SC Errata, Processor Revision 2.2 and 3.0" |
| in mips.md for details. |
| |
| [2] A similar bug to [1] exists for all revisions of the |
| R4000 and the R4400 when run in an MC configuration. |
| From "MIPS R4000MC Errata, Processor Revision 2.2 and 3.0": |
| |
| "19. In this following sequence: |
| |
| ddiv (or ddivu or div or divu) |
| dsll32 (or dsrl32, dsra32) |
| |
| if an MPT stall occurs, while the divide is slipping the cpu |
| pipeline, then the following double shift would end up with an |
| incorrect result. |
| |
| Workaround: The compiler needs to avoid generating any |
| sequence with divide followed by extended double shift." |
| |
| This erratum is also present in "MIPS R4400MC Errata, Processor |
| Revision 1.0" and "MIPS R4400MC Errata, Processor Revision 2.0 |
| & 3.0" as errata #10 and #4, respectively. |
| |
| [3] From "MIPS R4000PC/SC Errata, Processor Revision 2.2 and 3.0" |
| (also valid for MIPS R4000MC processors): |
| |
| "52. R4000SC: This bug does not apply for the R4000PC. |
| |
| There are two flavors of this bug: |
| |
| 1) If the instruction just after divide takes an RF exception |
| (tlb-refill, tlb-invalid) and gets an instruction cache |
| miss (both primary and secondary) and the line which is |
| currently in secondary cache at this index had the first |
| data word, where the bits 5..2 are set, then R4000 would |
| get a wrong result for the div. |
| |
| ##1 |
| nop |
| div r8, r9 |
| ------------------- # end-of page. -tlb-refill |
| nop |
| ##2 |
| nop |
| div r8, r9 |
| ------------------- # end-of page. -tlb-invalid |
| nop |
| |
| 2) If the divide is in the taken branch delay slot, where the |
| target takes RF exception and gets an I-cache miss for the |
| exception vector or where I-cache miss occurs for the |
| target address, under the above mentioned scenarios, the |
| div would get wrong results. |
| |
| ##1 |
| j r2 # to next page mapped or unmapped |
| div r8,r9 # this bug would be there as long |
| # as there is an ICache miss and |
| nop # the "data pattern" is present |
| |
| ##2 |
| beq r0, r0, NextPage # to Next page |
| div r8,r9 |
| nop |
| |
| This bug is present for div, divu, ddiv, and ddivu |
| instructions. |
| |
| Workaround: For item 1), OS could make sure that the next page |
| after the divide instruction is also mapped. For item 2), the |
| compiler could make sure that the divide instruction is not in |
| the branch delay slot." |
| |
| These processors have PRId values of 0x00004220 and 0x00004300 for |
| the R4000 and 0x00004400, 0x00004500 and 0x00004600 for the R4400. */ |
| |
| const char * |
| mips_output_division (const char *division, rtx *operands) |
| { |
| const char *s; |
| |
| s = division; |
| if (TARGET_FIX_R4000 || TARGET_FIX_R4400) |
| { |
| output_asm_insn (s, operands); |
| s = "nop"; |
| } |
| if (TARGET_CHECK_ZERO_DIV) |
| { |
| if (TARGET_MIPS16) |
| { |
| output_asm_insn (s, operands); |
| s = "bnez\t%2,1f\n\tbreak\t7\n1:"; |
| } |
| else if (GENERATE_DIVIDE_TRAPS) |
| { |
| output_asm_insn (s, operands); |
| s = "teq\t%2,%.,7"; |
| } |
| else |
| { |
| output_asm_insn ("%(bne\t%2,%.,1f", operands); |
| output_asm_insn (s, operands); |
| s = "break\t7%)\n1:"; |
| } |
| } |
| return s; |
| } |
| |
| /* Return true if GIVEN is the same as CANONICAL, or if it is CANONICAL |
| with a final "000" replaced by "k". Ignore case. |
| |
| Note: this function is shared between GCC and GAS. */ |
| |
| static bool |
| mips_strict_matching_cpu_name_p (const char *canonical, const char *given) |
| { |
| while (*given != 0 && TOLOWER (*given) == TOLOWER (*canonical)) |
| given++, canonical++; |
| |
| return ((*given == 0 && *canonical == 0) |
| || (strcmp (canonical, "000") == 0 && strcasecmp (given, "k") == 0)); |
| } |
| |
| |
| /* Return true if GIVEN matches CANONICAL, where GIVEN is a user-supplied |
| CPU name. We've traditionally allowed a lot of variation here. |
| |
| Note: this function is shared between GCC and GAS. */ |
| |
| static bool |
| mips_matching_cpu_name_p (const char *canonical, const char *given) |
| { |
| /* First see if the name matches exactly, or with a final "000" |
| turned into "k". */ |
| if (mips_strict_matching_cpu_name_p (canonical, given)) |
| return true; |
| |
| /* If not, try comparing based on numerical designation alone. |
| See if GIVEN is an unadorned number, or 'r' followed by a number. */ |
| if (TOLOWER (*given) == 'r') |
| given++; |
| if (!ISDIGIT (*given)) |
| return false; |
| |
| /* Skip over some well-known prefixes in the canonical name, |
| hoping to find a number there too. */ |
| if (TOLOWER (canonical[0]) == 'v' && TOLOWER (canonical[1]) == 'r') |
| canonical += 2; |
| else if (TOLOWER (canonical[0]) == 'r' && TOLOWER (canonical[1]) == 'm') |
| canonical += 2; |
| else if (TOLOWER (canonical[0]) == 'r') |
| canonical += 1; |
| |
| return mips_strict_matching_cpu_name_p (canonical, given); |
| } |
| |
| |
| /* Parse an option that takes the name of a processor as its argument. |
| OPTION is the name of the option and CPU_STRING is the argument. |
| Return the corresponding processor enumeration if the CPU_STRING is |
| recognized, otherwise report an error and return null. |
| |
| A similar function exists in GAS. */ |
| |
| static const struct mips_cpu_info * |
| mips_parse_cpu (const char *option, const char *cpu_string) |
| { |
| const struct mips_cpu_info *p; |
| const char *s; |
| |
| /* In the past, we allowed upper-case CPU names, but it doesn't |
| work well with the multilib machinery. */ |
| for (s = cpu_string; *s != 0; s++) |
| if (ISUPPER (*s)) |
| { |
| warning ("the cpu name must be lower case"); |
| break; |
| } |
| |
| /* 'from-abi' selects the most compatible architecture for the given |
| ABI: MIPS I for 32-bit ABIs and MIPS III for 64-bit ABIs. For the |
| EABIs, we have to decide whether we're using the 32-bit or 64-bit |
| version. Look first at the -mgp options, if given, otherwise base |
| the choice on MASK_64BIT in TARGET_DEFAULT. */ |
| if (strcasecmp (cpu_string, "from-abi") == 0) |
| return mips_cpu_info_from_isa (ABI_NEEDS_32BIT_REGS ? 1 |
| : ABI_NEEDS_64BIT_REGS ? 3 |
| : (TARGET_64BIT ? 3 : 1)); |
| |
| /* 'default' has traditionally been a no-op. Probably not very useful. */ |
| if (strcasecmp (cpu_string, "default") == 0) |
| return 0; |
| |
| for (p = mips_cpu_info_table; p->name != 0; p++) |
| if (mips_matching_cpu_name_p (p->name, cpu_string)) |
| return p; |
| |
| error ("bad value (%s) for %s", cpu_string, option); |
| return 0; |
| } |
| |
| |
| /* Return the processor associated with the given ISA level, or null |
| if the ISA isn't valid. */ |
| |
| static const struct mips_cpu_info * |
| mips_cpu_info_from_isa (int isa) |
| { |
| const struct mips_cpu_info *p; |
| |
| for (p = mips_cpu_info_table; p->name != 0; p++) |
| if (p->isa == isa) |
| return p; |
| |
| return 0; |
| } |
| |
| /* Implement HARD_REGNO_NREGS. The size of FP registers is controlled |
| by UNITS_PER_FPREG. The size of FP status registers is always 4, because |
| they only hold condition code modes, and CCmode is always considered to |
| be 4 bytes wide. All other registers are word sized. */ |
| |
| unsigned int |
| mips_hard_regno_nregs (int regno, enum machine_mode mode) |
| { |
| if (ST_REG_P (regno)) |
| return ((GET_MODE_SIZE (mode) + 3) / 4); |
| else if (! FP_REG_P (regno)) |
| return ((GET_MODE_SIZE (mode) + UNITS_PER_WORD - 1) / UNITS_PER_WORD); |
| else |
| return ((GET_MODE_SIZE (mode) + UNITS_PER_FPREG - 1) / UNITS_PER_FPREG); |
| } |
| |
| /* Implement TARGET_RETURN_IN_MEMORY. Under the old (i.e., 32 and O64 ABIs) |
| all BLKmode objects are returned in memory. Under the new (N32 and |
| 64-bit MIPS ABIs) small structures are returned in a register. |
| Objects with varying size must still be returned in memory, of |
| course. */ |
| |
| static bool |
| mips_return_in_memory (tree type, tree fndecl ATTRIBUTE_UNUSED) |
| { |
| if (TARGET_OLDABI) |
| return (TYPE_MODE (type) == BLKmode); |
| else |
| return ((int_size_in_bytes (type) > (2 * UNITS_PER_WORD)) |
| || (int_size_in_bytes (type) == -1)); |
| } |
| |
| static bool |
| mips_strict_argument_naming (CUMULATIVE_ARGS *ca ATTRIBUTE_UNUSED) |
| { |
| return !TARGET_OLDABI; |
| } |
| |
| /* Return true if INSN is a multiply-add or multiply-subtract |
| instruction and PREV assigns to the accumulator operand. */ |
| |
| bool |
| mips_linked_madd_p (rtx prev, rtx insn) |
| { |
| rtx x; |
| |
| x = single_set (insn); |
| if (x == 0) |
| return false; |
| |
| x = SET_SRC (x); |
| |
| if (GET_CODE (x) == PLUS |
| && GET_CODE (XEXP (x, 0)) == MULT |
| && reg_set_p (XEXP (x, 1), prev)) |
| return true; |
| |
| if (GET_CODE (x) == MINUS |
| && GET_CODE (XEXP (x, 1)) == MULT |
| && reg_set_p (XEXP (x, 0), prev)) |
| return true; |
| |
| return false; |
| } |
| |
| /* Used by TUNE_MACC_CHAINS to record the last scheduled instruction |
| that may clobber hi or lo. */ |
| |
| static rtx mips_macc_chains_last_hilo; |
| |
| /* A TUNE_MACC_CHAINS helper function. Record that instruction INSN has |
| been scheduled, updating mips_macc_chains_last_hilo appropriately. */ |
| |
| static void |
| mips_macc_chains_record (rtx insn) |
| { |
| if (get_attr_may_clobber_hilo (insn)) |
| mips_macc_chains_last_hilo = insn; |
| } |
| |
| /* A TUNE_MACC_CHAINS helper function. Search ready queue READY, which |
| has NREADY elements, looking for a multiply-add or multiply-subtract |
| instruction that is cumulative with mips_macc_chains_last_hilo. |
| If there is one, promote it ahead of anything else that might |
| clobber hi or lo. */ |
| |
| static void |
| mips_macc_chains_reorder (rtx *ready, int nready) |
| { |
| int i, j; |
| |
| if (mips_macc_chains_last_hilo != 0) |
| for (i = nready - 1; i >= 0; i--) |
| if (mips_linked_madd_p (mips_macc_chains_last_hilo, ready[i])) |
| { |
| for (j = nready - 1; j > i; j--) |
| if (recog_memoized (ready[j]) >= 0 |
| && get_attr_may_clobber_hilo (ready[j])) |
| { |
| mips_promote_ready (ready, i, j); |
| break; |
| } |
| break; |
| } |
| } |
| |
| /* The last instruction to be scheduled. */ |
| |
| static rtx vr4130_last_insn; |
| |
| /* A note_stores callback used by vr4130_true_reg_dependence_p. DATA |
| points to an rtx that is initially an instruction. Nullify the rtx |
| if the instruction uses the value of register X. */ |
| |
| static void |
| vr4130_true_reg_dependence_p_1 (rtx x, rtx pat ATTRIBUTE_UNUSED, void *data) |
| { |
| rtx *insn_ptr = data; |
| if (REG_P (x) |
| && *insn_ptr != 0 |
| && reg_referenced_p (x, PATTERN (*insn_ptr))) |
| *insn_ptr = 0; |
| } |
| |
| /* Return true if there is true register dependence between vr4130_last_insn |
| and INSN. */ |
| |
| static bool |
| vr4130_true_reg_dependence_p (rtx insn) |
| { |
| note_stores (PATTERN (vr4130_last_insn), |
| vr4130_true_reg_dependence_p_1, &insn); |
| return insn == 0; |
| } |
| |
| /* A TUNE_MIPS4130 helper function. Given that INSN1 is at the head of |
| the ready queue and that INSN2 is the instruction after it, return |
| true if it is worth promoting INSN2 ahead of INSN1. Look for cases |
| in which INSN1 and INSN2 can probably issue in parallel, but for |
| which (INSN2, INSN1) should be less sensitive to instruction |
| alignment than (INSN1, INSN2). See 4130.md for more details. */ |
| |
| static bool |
| vr4130_swap_insns_p (rtx insn1, rtx insn2) |
| { |
| rtx dep; |
| |
| /* Check for the following case: |
| |
| 1) there is some other instruction X with an anti dependence on INSN1; |
| 2) X has a higher priority than INSN2; and |
| 3) X is an arithmetic instruction (and thus has no unit restrictions). |
| |
| If INSN1 is the last instruction blocking X, it would better to |
| choose (INSN1, X) over (INSN2, INSN1). */ |
| for (dep = INSN_DEPEND (insn1); dep != 0; dep = XEXP (dep, 1)) |
| if (REG_NOTE_KIND (dep) == REG_DEP_ANTI |
| && INSN_PRIORITY (XEXP (dep, 0)) > INSN_PRIORITY (insn2) |
| && recog_memoized (XEXP (dep, 0)) >= 0 |
| && get_attr_vr4130_class (XEXP (dep, 0)) == VR4130_CLASS_ALU) |
| return false; |
| |
| if (vr4130_last_insn != 0 |
| && recog_memoized (insn1) >= 0 |
| && recog_memoized (insn2) >= 0) |
| { |
| /* See whether INSN1 and INSN2 use different execution units, |
| or if they are both ALU-type instructions. If so, they can |
| probably execute in parallel. */ |
| enum attr_vr4130_class class1 = get_attr_vr4130_class (insn1); |
| enum attr_vr4130_class class2 = get_attr_vr4130_class (insn2); |
| if (class1 != class2 || class1 == VR4130_CLASS_ALU) |
| { |
| /* If only one of the instructions has a dependence on |
| vr4130_last_insn, prefer to schedule the other one first. */ |
| bool dep1 = vr4130_true_reg_dependence_p (insn1); |
| bool dep2 = vr4130_true_reg_dependence_p (insn2); |
| if (dep1 != dep2) |
| return dep1; |
| |
| /* Prefer to schedule INSN2 ahead of INSN1 if vr4130_last_insn |
| is not an ALU-type instruction and if INSN1 uses the same |
| execution unit. (Note that if this condition holds, we already |
| know that INSN2 uses a different execution unit.) */ |
| if (class1 != VR4130_CLASS_ALU |
| && recog_memoized (vr4130_last_insn) >= 0 |
| && class1 == get_attr_vr4130_class (vr4130_last_insn)) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* A TUNE_MIPS4130 helper function. (READY, NREADY) describes a ready |
| queue with at least two instructions. Swap the first two if |
| vr4130_swap_insns_p says that it could be worthwhile. */ |
| |
| static void |
| vr4130_reorder (rtx *ready, int nready) |
| { |
| if (vr4130_swap_insns_p (ready[nready - 1], ready[nready - 2])) |
| mips_promote_ready (ready, nready - 2, nready - 1); |
| } |
| |
| /* Remove the instruction at index LOWER from ready queue READY and |
| reinsert it in front of the instruction at index HIGHER. LOWER must |
| be <= HIGHER. */ |
| |
| static void |
| mips_promote_ready (rtx *ready, int lower, int higher) |
| { |
| rtx new_head; |
| int i; |
| |
| new_head = ready[lower]; |
| for (i = lower; i < higher; i++) |
| ready[i] = ready[i + 1]; |
| ready[i] = new_head; |
| } |
| |
| /* Implement TARGET_SCHED_REORDER. */ |
| |
| static int |
| mips_sched_reorder (FILE *file ATTRIBUTE_UNUSED, int verbose ATTRIBUTE_UNUSED, |
| rtx *ready, int *nreadyp, int cycle) |
| { |
| if (!reload_completed && TUNE_MACC_CHAINS) |
| { |
| if (cycle == 0) |
| mips_macc_chains_last_hilo = 0; |
| if (*nreadyp > 0) |
| mips_macc_chains_reorder (ready, *nreadyp); |
| } |
| if (reload_completed && TUNE_MIPS4130 && !TARGET_VR4130_ALIGN) |
| { |
| if (cycle == 0) |
| vr4130_last_insn = 0; |
| if (*nreadyp > 1) |
| vr4130_reorder (ready, *nreadyp); |
| } |
| return mips_issue_rate (); |
| } |
| |
| /* Implement TARGET_SCHED_VARIABLE_ISSUE. */ |
| |
| static int |
| mips_variable_issue (FILE *file ATTRIBUTE_UNUSED, int verbose ATTRIBUTE_UNUSED, |
| rtx insn, int more) |
| { |
| switch (GET_CODE (PATTERN (insn))) |
| { |
| case USE: |
| case CLOBBER: |
| /* Don't count USEs and CLOBBERs against the issue rate. */ |
| break; |
| |
| default: |
| more--; |
| if (!reload_completed && TUNE_MACC_CHAINS) |
| mips_macc_chains_record (insn); |
| vr4130_last_insn = insn; |
| break; |
| } |
| return more; |
| } |
| |
| /* Implement TARGET_SCHED_ADJUST_COST. We assume that anti and output |
| dependencies have no cost. */ |
| |
| static int |
| mips_adjust_cost (rtx insn ATTRIBUTE_UNUSED, rtx link, |
| rtx dep ATTRIBUTE_UNUSED, int cost) |
| { |
| if (REG_NOTE_KIND (link) != 0) |
| return 0; |
| return cost; |
| } |
| |
| /* Return the number of instructions that can be issued per cycle. */ |
| |
| static int |
| mips_issue_rate (void) |
| { |
| switch (mips_tune) |
| { |
| case PROCESSOR_R4130: |
| case PROCESSOR_R5400: |
| case PROCESSOR_R5500: |
| case PROCESSOR_R7000: |
| case PROCESSOR_R9000: |
| return 2; |
| |
| case PROCESSOR_SB1: |
| /* This is actually 4, but we get better performance if we claim 3. |
| This is partly because of unwanted speculative code motion with the |
| larger number, and partly because in most common cases we can't |
| reach the theoretical max of 4. */ |
| return 3; |
| |
| default: |
| return 1; |
| } |
| } |
| |
| /* Implements TARGET_SCHED_FIRST_CYCLE_MULTIPASS_DFA_LOOKAHEAD. This should |
| be as wide as the scheduling freedom in the DFA. */ |
| |
| static int |
| mips_multipass_dfa_lookahead (void) |
| { |
| /* Can schedule up to 4 of the 6 function units in any one cycle. */ |
| if (mips_tune == PROCESSOR_SB1) |
| return 4; |
| |
| return 0; |
| } |
| |
| /* Given that we have an rtx of the form (prefetch ... WRITE LOCALITY), |
| return the first operand of the associated "pref" or "prefx" insn. */ |
| |
| rtx |
| mips_prefetch_cookie (rtx write, rtx locality) |
| { |
| /* store_streamed / load_streamed. */ |
| if (INTVAL (locality) <= 0) |
| return GEN_INT (INTVAL (write) + 4); |
| |
| /* store / load. */ |
| if (INTVAL (locality) <= 2) |
| return write; |
| |
| /* store_retained / load_retained. */ |
| return GEN_INT (INTVAL (write) + 6); |
| } |
| |
| /* MIPS builtin function support. */ |
| |
| struct builtin_description |
| { |
| /* The code of the main .md file instruction. See mips_builtin_type |
| for more information. */ |
| enum insn_code icode; |
| |
| /* The floating-point comparison code to use with ICODE, if any. */ |
| enum mips_fp_condition cond; |
| |
| /* The name of the builtin function. */ |
| const char *name; |
| |
| /* Specifies how the function should be expanded. */ |
| enum mips_builtin_type builtin_type; |
| |
| /* The function's prototype. */ |
| enum mips_function_type function_type; |
| |
| /* The target flags required for this function. */ |
| int target_flags; |
| }; |
| |
| /* Define a MIPS_BUILTIN_DIRECT function for instruction CODE_FOR_mips_<INSN>. |
| FUNCTION_TYPE and TARGET_FLAGS are builtin_description fields. */ |
| #define DIRECT_BUILTIN(INSN, FUNCTION_TYPE, TARGET_FLAGS) \ |
| { CODE_FOR_mips_ ## INSN, 0, "__builtin_mips_" #INSN, \ |
| MIPS_BUILTIN_DIRECT, FUNCTION_TYPE, TARGET_FLAGS } |
| |
| /* Define __builtin_mips_<INSN>_<COND>_{s,d}, both of which require |
| TARGET_FLAGS. */ |
| #define CMP_SCALAR_BUILTINS(INSN, COND, TARGET_FLAGS) \ |
| { CODE_FOR_mips_ ## INSN ## _cond_s, MIPS_FP_COND_ ## COND, \ |
| "__builtin_mips_" #INSN "_" #COND "_s", \ |
| MIPS_BUILTIN_CMP_SINGLE, MIPS_INT_FTYPE_SF_SF, TARGET_FLAGS }, \ |
| { CODE_FOR_mips_ ## INSN ## _cond_d, MIPS_FP_COND_ ## COND, \ |
| "__builtin_mips_" #INSN "_" #COND "_d", \ |
| MIPS_BUILTIN_CMP_SINGLE, MIPS_INT_FTYPE_DF_DF, TARGET_FLAGS } |
| |
| /* Define __builtin_mips_{any,all,upper,lower}_<INSN>_<COND>_ps. |
| The lower and upper forms require TARGET_FLAGS while the any and all |
| forms require MASK_MIPS3D. */ |
| #define CMP_PS_BUILTINS(INSN, COND, TARGET_FLAGS) \ |
| { CODE_FOR_mips_ ## INSN ## _cond_ps, MIPS_FP_COND_ ## COND, \ |
| "__builtin_mips_any_" #INSN "_" #COND "_ps", \ |
| MIPS_BUILTIN_CMP_ANY, MIPS_INT_FTYPE_V2SF_V2SF, MASK_MIPS3D }, \ |
| { CODE_FOR_mips_ ## INSN ## _cond_ps, MIPS_FP_COND_ ## COND, \ |
| "__builtin_mips_all_" #INSN "_" #COND "_ps", \ |
| MIPS_BUILTIN_CMP_ALL, MIPS_INT_FTYPE_V2SF_V2SF, MASK_MIPS3D }, \ |
| { CODE_FOR_mips_ ## INSN ## _cond_ps, MIPS_FP_COND_ ## COND, \ |
| "__builtin_mips_lower_" #INSN "_" #COND "_ps", \ |
| MIPS_BUILTIN_CMP_LOWER, MIPS_INT_FTYPE_V2SF_V2SF, TARGET_FLAGS }, \ |
| { CODE_FOR_mips_ ## INSN ## _cond_ps, MIPS_FP_COND_ ## COND, \ |
| "__builtin_mips_upper_" #INSN "_" #COND "_ps", \ |
| MIPS_BUILTIN_CMP_UPPER, MIPS_INT_FTYPE_V2SF_V2SF, TARGET_FLAGS } |
| |
| /* Define __builtin_mips_{any,all}_<INSN>_<COND>_4s. The functions |
| require MASK_MIPS3D. */ |
| #define CMP_4S_BUILTINS(INSN, COND) \ |
| { CODE_FOR_mips_ ## INSN ## _cond_4s, MIPS_FP_COND_ ## COND, \ |
| "__builtin_mips_any_" #INSN "_" #COND "_4s", \ |
| MIPS_BUILTIN_CMP_ANY, MIPS_INT_FTYPE_V2SF_V2SF_V2SF_V2SF, \ |
| MASK_MIPS3D }, \ |
| { CODE_FOR_mips_ ## INSN ## _cond_4s, MIPS_FP_COND_ ## COND, \ |
| "__builtin_mips_all_" #INSN "_" #COND "_4s", \ |
| MIPS_BUILTIN_CMP_ALL, MIPS_INT_FTYPE_V2SF_V2SF_V2SF_V2SF, \ |
| MASK_MIPS3D } |
| |
| /* Define __builtin_mips_mov{t,f}_<INSN>_<COND>_ps. The comparison |
| instruction requires TARGET_FLAGS. */ |
| #define MOVTF_BUILTINS(INSN, COND, TARGET_FLAGS) \ |
| { CODE_FOR_mips_ ## INSN ## _cond_ps, MIPS_FP_COND_ ## COND, \ |
| "__builtin_mips_movt_" #INSN "_" #COND "_ps", \ |
| MIPS_BUILTIN_MOVT, MIPS_V2SF_FTYPE_V2SF_V2SF_V2SF_V2SF, \ |
| TARGET_FLAGS }, \ |
| { CODE_FOR_mips_ ## INSN ## _cond_ps, MIPS_FP_COND_ ## COND, \ |
| "__builtin_mips_movf_" #INSN "_" #COND "_ps", \ |
| MIPS_BUILTIN_MOVF, MIPS_V2SF_FTYPE_V2SF_V2SF_V2SF_V2SF, \ |
| TARGET_FLAGS } |
| |
| /* Define all the builtins related to c.cond.fmt condition COND. */ |
| #define CMP_BUILTINS(COND) \ |
| MOVTF_BUILTINS (c, COND, MASK_PAIRED_SINGLE), \ |
| MOVTF_BUILTINS (cabs, COND, MASK_MIPS3D), \ |
| CMP_SCALAR_BUILTINS (cabs, COND, MASK_MIPS3D), \ |
| CMP_PS_BUILTINS (c, COND, MASK_PAIRED_SINGLE), \ |
| CMP_PS_BUILTINS (cabs, COND, MASK_MIPS3D), \ |
| CMP_4S_BUILTINS (c, COND), \ |
| CMP_4S_BUILTINS (cabs, COND) |
| |
| /* __builtin_mips_abs_ps() maps to the standard absM2 pattern. */ |
| #define CODE_FOR_mips_abs_ps CODE_FOR_absv2sf2 |
| |
| static const struct builtin_description mips_bdesc[] = |
| { |
| DIRECT_BUILTIN (pll_ps, MIPS_V2SF_FTYPE_V2SF_V2SF, MASK_PAIRED_SINGLE), |
| DIRECT_BUILTIN (pul_ps, MIPS_V2SF_FTYPE_V2SF_V2SF, MASK_PAIRED_SINGLE), |
| DIRECT_BUILTIN (plu_ps, MIPS_V2SF_FTYPE_V2SF_V2SF, MASK_PAIRED_SINGLE), |
| DIRECT_BUILTIN (puu_ps, MIPS_V2SF_FTYPE_V2SF_V2SF, MASK_PAIRED_SINGLE), |
| DIRECT_BUILTIN (cvt_ps_s, MIPS_V2SF_FTYPE_SF_SF, MASK_PAIRED_SINGLE), |
| DIRECT_BUILTIN (cvt_s_pl, MIPS_SF_FTYPE_V2SF, MASK_PAIRED_SINGLE), |
| DIRECT_BUILTIN (cvt_s_pu, MIPS_SF_FTYPE_V2SF, MASK_PAIRED_SINGLE), |
| DIRECT_BUILTIN (abs_ps, MIPS_V2SF_FTYPE_V2SF, MASK_PAIRED_SINGLE), |
| |
| DIRECT_BUILTIN (alnv_ps, MIPS_V2SF_FTYPE_V2SF_V2SF_INT, MASK_PAIRED_SINGLE), |
| DIRECT_BUILTIN (addr_ps, MIPS_V2SF_FTYPE_V2SF_V2SF, MASK_MIPS3D), |
| DIRECT_BUILTIN (mulr_ps, MIPS_V2SF_FTYPE_V2SF_V2SF, MASK_MIPS3D), |
| DIRECT_BUILTIN (cvt_pw_ps, MIPS_V2SF_FTYPE_V2SF, MASK_MIPS3D), |
| DIRECT_BUILTIN (cvt_ps_pw, MIPS_V2SF_FTYPE_V2SF, MASK_MIPS3D), |
| |
| DIRECT_BUILTIN (recip1_s, MIPS_SF_FTYPE_SF, MASK_MIPS3D), |
| DIRECT_BUILTIN (recip1_d, MIPS_DF_FTYPE_DF, MASK_MIPS3D), |
| DIRECT_BUILTIN (recip1_ps, MIPS_V2SF_FTYPE_V2SF, MASK_MIPS3D), |
| DIRECT_BUILTIN (recip2_s, MIPS_SF_FTYPE_SF_SF, MASK_MIPS3D), |
| DIRECT_BUILTIN (recip2_d, MIPS_DF_FTYPE_DF_DF, MASK_MIPS3D), |
| DIRECT_BUILTIN (recip2_ps, MIPS_V2SF_FTYPE_V2SF_V2SF, MASK_MIPS3D), |
| |
| DIRECT_BUILTIN (rsqrt1_s, MIPS_SF_FTYPE_SF, MASK_MIPS3D), |
| DIRECT_BUILTIN (rsqrt1_d, MIPS_DF_FTYPE_DF, MASK_MIPS3D), |
| DIRECT_BUILTIN (rsqrt1_ps, MIPS_V2SF_FTYPE_V2SF, MASK_MIPS3D), |
| DIRECT_BUILTIN (rsqrt2_s, MIPS_SF_FTYPE_SF_SF, MASK_MIPS3D), |
| DIRECT_BUILTIN (rsqrt2_d, MIPS_DF_FTYPE_DF_DF, MASK_MIPS3D), |
| DIRECT_BUILTIN (rsqrt2_ps, MIPS_V2SF_FTYPE_V2SF_V2SF, MASK_MIPS3D), |
| |
| MIPS_FP_CONDITIONS (CMP_BUILTINS) |
| }; |
| |
| /* Builtin functions for the SB-1 processor. */ |
| |
| #define CODE_FOR_mips_sqrt_ps CODE_FOR_sqrtv2sf2 |
| |
| static const struct builtin_description sb1_bdesc[] = |
| { |
| DIRECT_BUILTIN (sqrt_ps, MIPS_V2SF_FTYPE_V2SF, MASK_PAIRED_SINGLE) |
| }; |
| |
| /* This helps provide a mapping from builtin function codes to bdesc |
| arrays. */ |
| |
| struct bdesc_map |
| { |
| /* The builtin function table that this entry describes. */ |
| const struct builtin_description *bdesc; |
| |
| /* The number of entries in the builtin function table. */ |
| unsigned int size; |
| |
| /* The target processor that supports these builtin functions. |
| PROCESSOR_DEFAULT means we enable them for all processors. */ |
| enum processor_type proc; |
| }; |
| |
| static const struct bdesc_map bdesc_arrays[] = |
| { |
| { mips_bdesc, ARRAY_SIZE (mips_bdesc), PROCESSOR_DEFAULT }, |
| { sb1_bdesc, ARRAY_SIZE (sb1_bdesc), PROCESSOR_SB1 } |
| }; |
| |
| /* Take the head of argument list *ARGLIST and convert it into a form |
| suitable for input operand OP of instruction ICODE. Return the value |
| and point *ARGLIST at the next element of the list. */ |
| |
| static rtx |
| mips_prepare_builtin_arg (enum insn_code icode, |
| unsigned int op, tree *arglist) |
| { |
| rtx value; |
| enum machine_mode mode; |
| |
| value = expand_expr (TREE_VALUE (*arglist), NULL_RTX, VOIDmode, 0); |
| mode = insn_data[icode].operand[op].mode; |
| if (!insn_data[icode].operand[op].predicate (value, mode)) |
| value = copy_to_mode_reg (mode, value); |
| |
| *arglist = TREE_CHAIN (*arglist); |
| return value; |
| } |
| |
| /* Return an rtx suitable for output operand OP of instruction ICODE. |
| If TARGET is non-null, try to use it where possible. */ |
| |
| static rtx |
| mips_prepare_builtin_target (enum insn_code icode, unsigned int op, rtx target) |
| { |
| enum machine_mode mode; |
| |
| mode = insn_data[icode].operand[op].mode; |
| if (target == 0 || !insn_data[icode].operand[op].predicate (target, mode)) |
| target = gen_reg_rtx (mode); |
| |
| return target; |
| } |
| |
| /* Expand builtin functions. This is called from TARGET_EXPAND_BUILTIN. */ |
| |
| rtx |
| mips_expand_builtin (tree exp, rtx target, rtx subtarget ATTRIBUTE_UNUSED, |
| enum machine_mode mode ATTRIBUTE_UNUSED, |
| int ignore ATTRIBUTE_UNUSED) |
| { |
| enum insn_code icode; |
| enum mips_builtin_type type; |
| tree fndecl, arglist; |
| unsigned int fcode; |
| const struct builtin_description *bdesc; |
| const struct bdesc_map *m; |
| |
| fndecl = TREE_OPERAND (TREE_OPERAND (exp, 0), 0); |
| arglist = TREE_OPERAND (exp, 1); |
| fcode = DECL_FUNCTION_CODE (fndecl); |
| |
| bdesc = NULL; |
| for (m = bdesc_arrays; m < &bdesc_arrays[ARRAY_SIZE (bdesc_arrays)]; m++) |
| { |
| if (fcode < m->size) |
| { |
| bdesc = m->bdesc; |
| icode = bdesc[fcode].icode; |
| type = bdesc[fcode].builtin_type; |
| break; |
| } |
| fcode -= m->size; |
| } |
| if (bdesc == NULL) |
| return 0; |
| |
| switch (type) |
| { |
| case MIPS_BUILTIN_DIRECT: |
| return mips_expand_builtin_direct (icode, target, arglist); |
| |
| case MIPS_BUILTIN_MOVT: |
| case MIPS_BUILTIN_MOVF: |
| return mips_expand_builtin_movtf (type, icode, bdesc[fcode].cond, |
| target, arglist); |
| |
| case MIPS_BUILTIN_CMP_ANY: |
| case MIPS_BUILTIN_CMP_ALL: |
| case MIPS_BUILTIN_CMP_UPPER: |
| case MIPS_BUILTIN_CMP_LOWER: |
| case MIPS_BUILTIN_CMP_SINGLE: |
| return mips_expand_builtin_compare (type, icode, bdesc[fcode].cond, |
| target, arglist); |
| |
| default: |
| return 0; |
| } |
| } |
| |
| /* Init builtin functions. This is called from TARGET_INIT_BUILTIN. */ |
| |
| void |
| mips_init_builtins (void) |
| { |
| const struct builtin_description *d; |
| const struct bdesc_map *m; |
| tree types[(int) MIPS_MAX_FTYPE_MAX]; |
| tree V2SF_type_node; |
| unsigned int offset; |
| |
| /* We have only builtins for -mpaired-single and -mips3d. */ |
| if (!TARGET_PAIRED_SINGLE_FLOAT) |
| return; |
| |
| V2SF_type_node = build_vector_type_for_mode (float_type_node, V2SFmode); |
| |
| types[MIPS_V2SF_FTYPE_V2SF] |
| = build_function_type_list (V2SF_type_node, V2SF_type_node, NULL_TREE); |
| |
| types[MIPS_V2SF_FTYPE_V2SF_V2SF] |
| = build_function_type_list (V2SF_type_node, |
| V2SF_type_node, V2SF_type_node, NULL_TREE); |
| |
| types[MIPS_V2SF_FTYPE_V2SF_V2SF_INT] |
| = build_function_type_list (V2SF_type_node, |
| V2SF_type_node, V2SF_type_node, |
| integer_type_node, NULL_TREE); |
| |
| types[MIPS_V2SF_FTYPE_V2SF_V2SF_V2SF_V2SF] |
| = build_function_type_list (V2SF_type_node, |
| V2SF_type_node, V2SF_type_node, |
| V2SF_type_node, V2SF_type_node, NULL_TREE); |
| |
| types[MIPS_V2SF_FTYPE_SF_SF] |
| = build_function_type_list (V2SF_type_node, |
| float_type_node, float_type_node, NULL_TREE); |
| |
| types[MIPS_INT_FTYPE_V2SF_V2SF] |
| = build_function_type_list (integer_type_node, |
| V2SF_type_node, V2SF_type_node, NULL_TREE); |
| |
| types[MIPS_INT_FTYPE_V2SF_V2SF_V2SF_V2SF] |
| = build_function_type_list (integer_type_node, |
| V2SF_type_node, V2SF_type_node, |
| V2SF_type_node, V2SF_type_node, NULL_TREE); |
| |
| types[MIPS_INT_FTYPE_SF_SF] |
| = build_function_type_list (integer_type_node, |
| float_type_node, float_type_node, NULL_TREE); |
| |
| types[MIPS_INT_FTYPE_DF_DF] |
| = build_function_type_list (integer_type_node, |
| double_type_node, double_type_node, NULL_TREE); |
| |
| types[MIPS_SF_FTYPE_V2SF] |
| = build_function_type_list (float_type_node, V2SF_type_node, NULL_TREE); |
| |
| types[MIPS_SF_FTYPE_SF] |
| = build_function_type_list (float_type_node, |
| float_type_node, NULL_TREE); |
| |
| types[MIPS_SF_FTYPE_SF_SF] |
| = build_function_type_list (float_type_node, |
| float_type_node, float_type_node, NULL_TREE); |
| |
| types[MIPS_DF_FTYPE_DF] |
| = build_function_type_list (double_type_node, |
| double_type_node, NULL_TREE); |
| |
| types[MIPS_DF_FTYPE_DF_DF] |
| = build_function_type_list (double_type_node, |
| double_type_node, double_type_node, NULL_TREE); |
| |
| /* Iterate through all of the bdesc arrays, initializing all of the |
| builtin functions. */ |
| |
| offset = 0; |
| for (m = bdesc_arrays; m < &bdesc_arrays[ARRAY_SIZE (bdesc_arrays)]; m++) |
| { |
| if (m->proc == PROCESSOR_DEFAULT || (m->proc == mips_arch)) |
| for (d = m->bdesc; d < &m->bdesc[m->size]; d++) |
| if ((d->target_flags & target_flags) == d->target_flags) |
| lang_hooks.builtin_function (d->name, types[d->function_type], |
| d - m->bdesc + offset, |
| BUILT_IN_MD, NULL, NULL); |
| offset += m->size; |
| } |
| } |
| |
| /* Expand a MIPS_BUILTIN_DIRECT function. ICODE is the code of the |
| .md pattern and ARGLIST is the list of function arguments. TARGET, |
| if nonnull, suggests a good place to put the result. */ |
| |
| static rtx |
| mips_expand_builtin_direct (enum insn_code icode, rtx target, tree arglist) |
| { |
| rtx ops[MAX_RECOG_OPERANDS]; |
| int i; |
| |
| target = mips_prepare_builtin_target (icode, 0, target); |
| for (i = 1; i < insn_data[icode].n_operands; i++) |
| ops[i] = mips_prepare_builtin_arg (icode, i, &arglist); |
| |
| switch (insn_data[icode].n_operands) |
| { |
| case 2: |
| emit_insn (GEN_FCN (icode) (target, ops[1])); |
| break; |
| |
| case 3: |
| emit_insn (GEN_FCN (icode) (target, ops[1], ops[2])); |
| break; |
| |
| case 4: |
| emit_insn (GEN_FCN (icode) (target, ops[1], ops[2], ops[3])); |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| return target; |
| } |
| |
| /* Expand a __builtin_mips_movt_*_ps() or __builtin_mips_movf_*_ps() |
| function (TYPE says which). ARGLIST is the list of arguments to the |
| function, ICODE is the instruction that should be used to compare |
| the first two arguments, and COND is the condition it should test. |
| TARGET, if nonnull, suggests a good place to put the result. */ |
| |
| static rtx |
| mips_expand_builtin_movtf (enum mips_builtin_type type, |
| enum insn_code icode, enum mips_fp_condition cond, |
| rtx target, tree arglist) |
| { |
| rtx cmp_result, op0, op1; |
| |
| cmp_result = mips_prepare_builtin_target (icode, 0, 0); |
| op0 = mips_prepare_builtin_arg (icode, 1, &arglist); |
| op1 = mips_prepare_builtin_arg (icode, 2, &arglist); |
| emit_insn (GEN_FCN (icode) (cmp_result, op0, op1, GEN_INT (cond))); |
| |
| icode = CODE_FOR_mips_cond_move_tf_ps; |
| target = mips_prepare_builtin_target (icode, 0, target); |
| if (type == MIPS_BUILTIN_MOVT) |
| { |
| op1 = mips_prepare_builtin_arg (icode, 2, &arglist); |
| op0 = mips_prepare_builtin_arg (icode, 1, &arglist); |
| } |
| else |
| { |
| op0 = mips_prepare_builtin_arg (icode, 1, &arglist); |
| op1 = mips_prepare_builtin_arg (icode, 2, &arglist); |
| } |
| emit_insn (gen_mips_cond_move_tf_ps (target, op0, op1, cmp_result)); |
| return target; |
| } |
| |
| /* Expand a comparison builtin of type BUILTIN_TYPE. ICODE is the code |
| of the comparison instruction and COND is the condition it should test. |
| ARGLIST is the list of function arguments and TARGET, if nonnull, |
| suggests a good place to put the boolean result. */ |
| |
| static rtx |
| mips_expand_builtin_compare (enum mips_builtin_type builtin_type, |
| enum insn_code icode, enum mips_fp_condition cond, |
| rtx target, tree arglist) |
| { |
| rtx label1, label2, if_then_else; |
| rtx pat, cmp_result, ops[MAX_RECOG_OPERANDS]; |
| rtx target_if_equal, target_if_unequal; |
| int cmp_value, i; |
| |
| if (target == 0 || GET_MODE (target) != SImode) |
| target = gen_reg_rtx (SImode); |
| |
| /* Prepare the operands to the comparison. */ |
| cmp_result = mips_prepare_builtin_target (icode, 0, 0); |
| for (i = 1; i < insn_data[icode].n_operands - 1; i++) |
| ops[i] = mips_prepare_builtin_arg (icode, i, &arglist); |
| |
| switch (insn_data[icode].n_operands) |
| { |
| case 4: |
| pat = GEN_FCN (icode) (cmp_result, ops[1], ops[2], GEN_INT (cond)); |
| break; |
| |
| case 6: |
| pat = GEN_FCN (icode) (cmp_result, ops[1], ops[2], |
| ops[3], ops[4], GEN_INT (cond)); |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| /* If the comparison sets more than one register, we define the result |
| to be 0 if all registers are false and -1 if all registers are true. |
| The value of the complete result is indeterminate otherwise. It is |
| possible to test individual registers using SUBREGs. |
| |
| Set up CMP_RESULT, CMP_VALUE, TARGET_IF_EQUAL and TARGET_IF_UNEQUAL so |
| that the result should be TARGET_IF_EQUAL if (EQ CMP_RESULT CMP_VALUE) |
| and TARGET_IF_UNEQUAL otherwise. */ |
| if (builtin_type == MIPS_BUILTIN_CMP_ALL) |
| { |
| cmp_value = -1; |
| target_if_equal = const1_rtx; |
| target_if_unequal = const0_rtx; |
| } |
| else |
| { |
| cmp_value = 0; |
| target_if_equal = const0_rtx; |
| target_if_unequal = const1_rtx; |
| if (builtin_type == MIPS_BUILTIN_CMP_UPPER) |
| cmp_result = simplify_gen_subreg (CCmode, cmp_result, CCV2mode, 4); |
| else if (builtin_type == MIPS_BUILTIN_CMP_LOWER) |
| cmp_result = simplify_gen_subreg (CCmode, cmp_result, CCV2mode, 0); |
| } |
| |
| /* First assume that CMP_RESULT == CMP_VALUE. */ |
| emit_move_insn (target, target_if_equal); |
| |
| /* Branch to LABEL1 if CMP_RESULT != CMP_VALUE. */ |
| emit_insn (pat); |
| label1 = gen_label_rtx (); |
| label2 = gen_label_rtx (); |
| if_then_else |
| = gen_rtx_IF_THEN_ELSE (VOIDmode, |
| gen_rtx_fmt_ee (NE, GET_MODE (cmp_result), |
| cmp_result, GEN_INT (cmp_value)), |
| gen_rtx_LABEL_REF (VOIDmode, label1), pc_rtx); |
| emit_jump_insn (gen_rtx_SET (VOIDmode, pc_rtx, if_then_else)); |
| emit_jump_insn (gen_rtx_SET (VOIDmode, pc_rtx, |
| gen_rtx_LABEL_REF (VOIDmode, label2))); |
| emit_barrier (); |
| emit_label (label1); |
| |
| /* Fix TARGET for CMP_RESULT != CMP_VALUE. */ |
| emit_move_insn (target, target_if_unequal); |
| emit_label (label2); |
| |
| return target; |
| } |
| |
| #include "gt-mips.h" |