| /* Subroutines used for MIPS code generation. |
| Copyright (C) 1989, 1990, 1991, 1993, 1994, 1995, 1996, 1997, 1998, |
| 1999, 2000, 2001, 2002, 2003, 2004 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" |
| |
| /* Enumeration for all of the relational tests, so that we can build |
| arrays indexed by the test type, and not worry about the order |
| of EQ, NE, etc. */ |
| |
| enum internal_test { |
| ITEST_EQ, |
| ITEST_NE, |
| ITEST_GT, |
| ITEST_GE, |
| ITEST_LT, |
| ITEST_LE, |
| ITEST_GTU, |
| ITEST_GEU, |
| ITEST_LTU, |
| ITEST_LEU, |
| ITEST_MAX |
| }; |
| |
| /* Return true if it is likely that the given mode will be accessed |
| using only a single instruction. */ |
| #define SINGLE_WORD_MODE_P(MODE) \ |
| ((MODE) != BLKmode && GET_MODE_SIZE (MODE) <= UNITS_PER_WORD) |
| |
| /* 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)) |
| |
| /* True if X is (const (unspec [(const_int 0)] UNSPEC_GP)). This is used |
| to initialize the mips16 gp pseudo register. */ |
| #define CONST_GP_P(X) \ |
| (GET_CODE (X) == CONST \ |
| && GET_CODE (XEXP (X, 0)) == UNSPEC \ |
| && XINT (XEXP (X, 0), 1) == UNSPEC_GP) |
| |
| /* 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. It could really be 0x7ff0, |
| but SGI's assemblers implement daddiu $sp,$sp,-0x7ff0 as a |
| multi-instruction addu sequence. Use 0x7fe0 to work around this. */ |
| #define MIPS_MAX_FIRST_STACK_STEP (TARGET_MIPS16 ? 0x100 : 0x7fe0) |
| |
| /* Classifies a SYMBOL_REF, LABEL_REF or UNSPEC address. |
| |
| SYMBOL_GENERAL |
| Used when none of the below apply. |
| |
| SYMBOL_SMALL_DATA |
| The symbol refers to something in a small data section. |
| |
| SYMBOL_CONSTANT_POOL |
| The symbol refers to something in the mips16 constant pool. |
| |
| SYMBOL_GOT_LOCAL |
| The symbol refers to local data that will be found using |
| the global offset table. |
| |
| SYMBOL_GOT_GLOBAL |
| Likewise non-local data. |
| |
| SYMBOL_GOTOFF_PAGE |
| An UNSPEC wrapper around a SYMBOL_GOT_LOCAL. It represents the |
| offset from _gp of a GOT page entry. |
| |
| SYMBOL_GOTOFF_GLOBAL |
| An UNSPEC wrapper around a SYMBOL_GOT_GLOBAL. It represents the |
| the offset from _gp of the symbol's GOT entry. |
| |
| SYMBOL_GOTOFF_CALL |
| Like SYMBOL_GOTOFF_GLOBAL, but used when calling a global function. |
| The GOT entry is allowed to point to a stub rather than to the |
| function itself. |
| |
| SYMBOL_GOTOFF_LOADGP |
| An UNSPEC wrapper around a function's address. It represents the |
| offset of _gp from the start of the function. */ |
| enum mips_symbol_type { |
| SYMBOL_GENERAL, |
| SYMBOL_SMALL_DATA, |
| SYMBOL_CONSTANT_POOL, |
| SYMBOL_GOT_LOCAL, |
| SYMBOL_GOT_GLOBAL, |
| SYMBOL_GOTOFF_PAGE, |
| SYMBOL_GOTOFF_GLOBAL, |
| SYMBOL_GOTOFF_CALL, |
| SYMBOL_GOTOFF_LOADGP |
| }; |
| #define NUM_SYMBOL_TYPES (SYMBOL_GOTOFF_LOADGP + 1) |
| |
| |
| /* 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 |
| }; |
| |
| /* 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 constant; |
| struct mips_arg_info; |
| struct mips_address_info; |
| struct mips_integer_op; |
| |
| 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_symbolic_constant_p (rtx, enum mips_symbol_type *); |
| 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_address (rtx, enum mips_symbol_type); |
| static rtx mips_unspec_offset_high (rtx, rtx, rtx, enum mips_symbol_type); |
| static rtx mips_load_got (rtx, rtx, enum mips_symbol_type); |
| static rtx mips_add_offset (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 enum internal_test map_test_to_internal_test (enum rtx_code); |
| static void get_float_compare_codes (enum rtx_code, enum rtx_code *, |
| enum rtx_code *); |
| 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 **); |
| static bool mips_assemble_integer (rtx, unsigned int, int); |
| static void mips_file_start (void); |
| static void mips_file_end (void); |
| static bool mips_rewrite_small_data_p (rtx); |
| static int 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_select_section (tree, int, unsigned HOST_WIDE_INT) |
| ATTRIBUTE_UNUSED; |
| static bool mips_in_small_data_p (tree); |
| static void mips_encode_section_info (tree, rtx, int); |
| 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 add_constant (struct constant **, rtx, enum machine_mode); |
| static void dump_constants (struct constant *, rtx); |
| static rtx mips_find_symbol (rtx); |
| static void mips16_lay_out_constants (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 int mips_adjust_cost (rtx, rtx, rtx, int); |
| static int mips_issue_rate (void); |
| static int mips_use_dfa_pipeline_interface (void); |
| static void mips_init_libfuncs (void); |
| static tree mips_build_builtin_va_list (void); |
| |
| #if TARGET_IRIX |
| static void irix_asm_named_section_1 (const char *, unsigned int, |
| unsigned int); |
| static void irix_asm_named_section (const char *, unsigned int); |
| static int irix_section_align_entry_eq (const void *, const void *); |
| static hashval_t irix_section_align_entry_hash (const void *); |
| static void irix_file_start (void); |
| static int irix_section_align_1 (void **, void *); |
| static void copy_file_data (FILE *, FILE *); |
| static void irix_file_end (void); |
| static unsigned int irix_section_type_flags (tree, const char *, int); |
| #endif |
| |
| /* 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 address of the current function when |
| generating embedded PIC code. */ |
| rtx embedded_pic_fnaddr_rtx; |
| |
| /* 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; |
| |
| /* Length of instructions in function; mips16 only. */ |
| long insns_len; |
| |
| /* 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 argument's size, in bytes. */ |
| unsigned int num_bytes; |
| |
| /* The number of words passed in registers, rounded up. */ |
| unsigned int reg_words; |
| |
| /* The offset of the first register from GP_ARG_FIRST or FP_ARG_FIRST, |
| or 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; |
| |
| /* Cached operands, and operator to compare for use in set/branch/trap |
| on condition codes. */ |
| rtx branch_cmp[2]; |
| |
| /* what type of branch to use */ |
| enum cmp_type branch_type; |
| |
| /* 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; |
| |
| /* 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]; |
| |
| /* The length of all strings seen when compiling for the mips16. This |
| is used to tell how many strings are in the constant pool, so that |
| we can see if we may have an overflow. This is reset each time the |
| constant pool is output. */ |
| int mips_string_length; |
| |
| /* When generating mips16 code, a list of all strings that are to be |
| output after the current function. */ |
| |
| static GTY(()) rtx mips16_strings; |
| |
| /* In mips16 mode, we build a list of all the string constants we see |
| in a particular function. */ |
| |
| struct string_constant |
| { |
| struct string_constant *next; |
| const char *label; |
| }; |
| |
| static struct string_constant *string_constants; |
| |
| /* 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]; |
| |
| /* An alias set for the GOT. */ |
| static GTY(()) int mips_got_alias_set; |
| |
| /* 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]; |
| |
| /* Hardware names for the registers. If -mrnames is used, this |
| will be overwritten with mips_sw_reg_names. */ |
| |
| char mips_reg_names[][8] = |
| { |
| "$0", "$1", "$2", "$3", "$4", "$5", "$6", "$7", |
| "$8", "$9", "$10", "$11", "$12", "$13", "$14", "$15", |
| "$16", "$17", "$18", "$19", "$20", "$21", "$22", "$23", |
| "$24", "$25", "$26", "$27", "$28", "$sp", "$fp", "$31", |
| "$f0", "$f1", "$f2", "$f3", "$f4", "$f5", "$f6", "$f7", |
| "$f8", "$f9", "$f10", "$f11", "$f12", "$f13", "$f14", "$f15", |
| "$f16", "$f17", "$f18", "$f19", "$f20", "$f21", "$f22", "$f23", |
| "$f24", "$f25", "$f26", "$f27", "$f28", "$f29", "$f30", "$f31", |
| "hi", "lo", "", "$fcc0","$fcc1","$fcc2","$fcc3","$fcc4", |
| "$fcc5","$fcc6","$fcc7","", "", "$arg", "$frame", "$fakec", |
| "$c0r0", "$c0r1", "$c0r2", "$c0r3", "$c0r4", "$c0r5", "$c0r6", "$c0r7", |
| "$c0r8", "$c0r9", "$c0r10","$c0r11","$c0r12","$c0r13","$c0r14","$c0r15", |
| "$c0r16","$c0r17","$c0r18","$c0r19","$c0r20","$c0r21","$c0r22","$c0r23", |
| "$c0r24","$c0r25","$c0r26","$c0r27","$c0r28","$c0r29","$c0r30","$c0r31", |
| "$c2r0", "$c2r1", "$c2r2", "$c2r3", "$c2r4", "$c2r5", "$c2r6", "$c2r7", |
| "$c2r8", "$c2r9", "$c2r10","$c2r11","$c2r12","$c2r13","$c2r14","$c2r15", |
| "$c2r16","$c2r17","$c2r18","$c2r19","$c2r20","$c2r21","$c2r22","$c2r23", |
| "$c2r24","$c2r25","$c2r26","$c2r27","$c2r28","$c2r29","$c2r30","$c2r31", |
| "$c3r0", "$c3r1", "$c3r2", "$c3r3", "$c3r4", "$c3r5", "$c3r6", "$c3r7", |
| "$c3r8", "$c3r9", "$c3r10","$c3r11","$c3r12","$c3r13","$c3r14","$c3r15", |
| "$c3r16","$c3r17","$c3r18","$c3r19","$c3r20","$c3r21","$c3r22","$c3r23", |
| "$c3r24","$c3r25","$c3r26","$c3r27","$c3r28","$c3r29","$c3r30","$c3r31" |
| }; |
| |
| /* Mips software names for the registers, used to overwrite the |
| mips_reg_names array. */ |
| |
| char mips_sw_reg_names[][8] = |
| { |
| "$zero","$at", "$v0", "$v1", "$a0", "$a1", "$a2", "$a3", |
| "$t0", "$t1", "$t2", "$t3", "$t4", "$t5", "$t6", "$t7", |
| "$s0", "$s1", "$s2", "$s3", "$s4", "$s5", "$s6", "$s7", |
| "$t8", "$t9", "$k0", "$k1", "$gp", "$sp", "$fp", "$ra", |
| "$f0", "$f1", "$f2", "$f3", "$f4", "$f5", "$f6", "$f7", |
| "$f8", "$f9", "$f10", "$f11", "$f12", "$f13", "$f14", "$f15", |
| "$f16", "$f17", "$f18", "$f19", "$f20", "$f21", "$f22", "$f23", |
| "$f24", "$f25", "$f26", "$f27", "$f28", "$f29", "$f30", "$f31", |
| "hi", "lo", "", "$fcc0","$fcc1","$fcc2","$fcc3","$fcc4", |
| "$fcc5","$fcc6","$fcc7","$rap", "", "$arg", "$frame", "$fakec", |
| "$c0r0", "$c0r1", "$c0r2", "$c0r3", "$c0r4", "$c0r5", "$c0r6", "$c0r7", |
| "$c0r8", "$c0r9", "$c0r10","$c0r11","$c0r12","$c0r13","$c0r14","$c0r15", |
| "$c0r16","$c0r17","$c0r18","$c0r19","$c0r20","$c0r21","$c0r22","$c0r23", |
| "$c0r24","$c0r25","$c0r26","$c0r27","$c0r28","$c0r29","$c0r30","$c0r31", |
| "$c2r0", "$c2r1", "$c2r2", "$c2r3", "$c2r4", "$c2r5", "$c2r6", "$c2r7", |
| "$c2r8", "$c2r9", "$c2r10","$c2r11","$c2r12","$c2r13","$c2r14","$c2r15", |
| "$c2r16","$c2r17","$c2r18","$c2r19","$c2r20","$c2r21","$c2r22","$c2r23", |
| "$c2r24","$c2r25","$c2r26","$c2r27","$c2r28","$c2r29","$c2r30","$c2r31", |
| "$c3r0", "$c3r1", "$c3r2", "$c3r3", "$c3r4", "$c3r5", "$c3r6", "$c3r7", |
| "$c3r8", "$c3r9", "$c3r10","$c3r11","$c3r12","$c3r13","$c3r14","$c3r15", |
| "$c3r16","$c3r17","$c3r18","$c3r19","$c3r20","$c3r21","$c3r22","$c3r23", |
| "$c3r24","$c3r25","$c3r26","$c3r27","$c3r28","$c3r29","$c3r30","$c3r31" |
| }; |
| |
| /* 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 }, |
| { "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_INTEGER |
| #define TARGET_ASM_INTEGER mips_assemble_integer |
| |
| #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_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_USE_DFA_PIPELINE_INTERFACE |
| #define TARGET_SCHED_USE_DFA_PIPELINE_INTERFACE mips_use_dfa_pipeline_interface |
| |
| #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_ENCODE_SECTION_INFO |
| #define TARGET_ENCODE_SECTION_INFO mips_encode_section_info |
| #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 |
| #if TARGET_IRIX |
| #define TARGET_ASM_FILE_START irix_file_start |
| #define TARGET_ASM_FILE_END irix_file_end |
| #else |
| #define TARGET_ASM_FILE_START mips_file_start |
| #define TARGET_ASM_FILE_END mips_file_end |
| #endif |
| #undef TARGET_ASM_FILE_START_FILE_DIRECTIVE |
| #define TARGET_ASM_FILE_START_FILE_DIRECTIVE true |
| |
| #if TARGET_IRIX |
| #undef TARGET_SECTION_TYPE_FLAGS |
| #define TARGET_SECTION_TYPE_FLAGS irix_section_type_flags |
| #endif |
| |
| #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_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 |
| |
| 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) |
| return (TARGET_ABICALLS ? SYMBOL_GOT_LOCAL : SYMBOL_GENERAL); |
| |
| if (GET_CODE (x) != SYMBOL_REF) |
| abort (); |
| |
| 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; |
| |
| /* When generating mips16 code, SYMBOL_REF_FLAG indicates a string |
| in the current function's constant pool. */ |
| if (TARGET_MIPS16 && SYMBOL_REF_FLAG (x)) |
| return SYMBOL_CONSTANT_POOL; |
| |
| 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. */ |
| |
| static 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: |
| /* 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_SMALL_DATA: |
| case SYMBOL_CONSTANT_POOL: |
| /* 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; |
| } |
| abort (); |
| } |
| |
| |
| /* 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 consitent 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 (GET_CODE (x) == REG |
| && 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, sw, ld and sd. */ |
| 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: |
| return true; |
| } |
| abort (); |
| } |
| |
| |
| /* 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: |
| /* Check whether the offset is a 16- or 32-bit value. */ |
| return mips_split_p[type] ? 2 : 1; |
| } |
| abort (); |
| } |
| |
| |
| /* 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 CONSTANT_P_RTX: |
| return 1; |
| |
| 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: |
| 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) |
| { |
| if (GET_CODE (x) != MEM) |
| abort (); |
| |
| return mips_address_insns (XEXP (x, 0), GET_MODE (x)); |
| } |
| |
| |
| /* Return truth value of whether OP can be used as an operands |
| where a register or 16 bit unsigned integer is needed. */ |
| |
| int |
| uns_arith_operand (rtx op, enum machine_mode mode) |
| { |
| if (GET_CODE (op) == CONST_INT && SMALL_INT_UNSIGNED (op)) |
| return 1; |
| |
| return register_operand (op, mode); |
| } |
| |
| |
| /* True if OP can be treated as a signed 16-bit constant. */ |
| |
| int |
| const_arith_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return GET_CODE (op) == CONST_INT && SMALL_INT (op); |
| } |
| |
| |
| /* Return true if OP is a register operand or a signed 16-bit constant. */ |
| |
| int |
| arith_operand (rtx op, enum machine_mode mode) |
| { |
| return const_arith_operand (op, mode) || register_operand (op, mode); |
| } |
| |
| /* Return truth value of whether OP is an integer which fits in 16 bits. */ |
| |
| int |
| small_int (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return (GET_CODE (op) == CONST_INT && SMALL_INT (op)); |
| } |
| |
| /* Return truth value of whether OP is a register or the constant 0. |
| Do not accept 0 in mips16 mode since $0 is not one of the core 8 |
| registers. */ |
| |
| int |
| reg_or_0_operand (rtx op, enum machine_mode mode) |
| { |
| switch (GET_CODE (op)) |
| { |
| case CONST_INT: |
| if (TARGET_MIPS16) |
| return 0; |
| return INTVAL (op) == 0; |
| |
| case CONST_DOUBLE: |
| if (TARGET_MIPS16) |
| return 0; |
| return op == CONST0_RTX (mode); |
| |
| default: |
| return register_operand (op, mode); |
| } |
| } |
| |
| /* Accept a register or the floating point constant 1 in the appropriate mode. */ |
| |
| int |
| reg_or_const_float_1_operand (rtx op, enum machine_mode mode) |
| { |
| REAL_VALUE_TYPE d; |
| |
| switch (GET_CODE (op)) |
| { |
| case CONST_DOUBLE: |
| if (mode != GET_MODE (op) |
| || (mode != DFmode && mode != SFmode)) |
| return 0; |
| |
| REAL_VALUE_FROM_CONST_DOUBLE (d, op); |
| return REAL_VALUES_EQUAL (d, dconst1); |
| |
| default: |
| return register_operand (op, mode); |
| } |
| } |
| |
| /* Accept the floating point constant 1 in the appropriate mode. */ |
| |
| int |
| const_float_1_operand (rtx op, enum machine_mode mode) |
| { |
| REAL_VALUE_TYPE d; |
| |
| if (GET_CODE (op) != CONST_DOUBLE |
| || mode != GET_MODE (op) |
| || (mode != DFmode && mode != SFmode)) |
| return 0; |
| |
| REAL_VALUE_FROM_CONST_DOUBLE (d, op); |
| |
| return REAL_VALUES_EQUAL (d, dconst1); |
| } |
| |
| /* Return true if OP is either the HI or LO register. */ |
| |
| int |
| hilo_operand (rtx op, enum machine_mode mode) |
| { |
| return ((mode == VOIDmode || mode == GET_MODE (op)) |
| && REG_P (op) && MD_REG_P (REGNO (op))); |
| } |
| |
| /* Return true if OP is an extension operator. */ |
| |
| int |
| extend_operator (rtx op, enum machine_mode mode) |
| { |
| return ((mode == VOIDmode || mode == GET_MODE (op)) |
| && (GET_CODE (op) == ZERO_EXTEND || GET_CODE (op) == SIGN_EXTEND)); |
| } |
| |
| /* Return nonzero if the code of this rtx pattern is EQ or NE. */ |
| |
| int |
| equality_op (rtx op, enum machine_mode mode) |
| { |
| if (mode != GET_MODE (op)) |
| return 0; |
| |
| return GET_CODE (op) == EQ || GET_CODE (op) == NE; |
| } |
| |
| /* Return nonzero if the code is a relational operations (EQ, LE, etc.) */ |
| |
| int |
| cmp_op (rtx op, enum machine_mode mode) |
| { |
| if (mode != GET_MODE (op)) |
| return 0; |
| |
| return GET_RTX_CLASS (GET_CODE (op)) == '<'; |
| } |
| |
| /* Return nonzero if the code is a relational operation suitable for a |
| conditional trap instruction (only EQ, NE, LT, LTU, GE, GEU). |
| We need this in the insn that expands `trap_if' in order to prevent |
| combine from erroneously altering the condition. */ |
| |
| int |
| trap_cmp_op (rtx op, enum machine_mode mode) |
| { |
| if (mode != GET_MODE (op)) |
| return 0; |
| |
| switch (GET_CODE (op)) |
| { |
| case EQ: |
| case NE: |
| case LT: |
| case LTU: |
| case GE: |
| case GEU: |
| return 1; |
| |
| default: |
| return 0; |
| } |
| } |
| |
| /* Return nonzero if the operand is either the PC or a label_ref. */ |
| |
| int |
| pc_or_label_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| if (op == pc_rtx) |
| return 1; |
| |
| if (GET_CODE (op) == LABEL_REF) |
| return 1; |
| |
| return 0; |
| } |
| |
| /* Test for a valid call address. */ |
| |
| int |
| call_insn_operand (rtx op, enum machine_mode mode) |
| { |
| enum mips_symbol_type symbol_type; |
| |
| if (mips_symbolic_constant_p (op, &symbol_type)) |
| switch (symbol_type) |
| { |
| case SYMBOL_GENERAL: |
| /* If -mlong-calls, force all calls to use register addressing. */ |
| return !TARGET_LONG_CALLS; |
| |
| case SYMBOL_GOT_GLOBAL: |
| /* Without explicit relocs, there is no special syntax for |
| loading the address of a call destination into a register. |
| Using "la $25,foo; jal $25" would prevent the lazy binding |
| of "foo", so keep the address of global symbols with the |
| jal macro. */ |
| return !TARGET_EXPLICIT_RELOCS; |
| |
| default: |
| return false; |
| } |
| return register_operand (op, mode); |
| } |
| |
| |
| /* Return nonzero if OP is valid as a source operand for a move |
| instruction. */ |
| |
| int |
| move_operand (rtx op, enum machine_mode mode) |
| { |
| enum mips_symbol_type symbol_type; |
| |
| if (!general_operand (op, mode)) |
| return false; |
| |
| switch (GET_CODE (op)) |
| { |
| case CONST_INT: |
| /* When generating mips16 code, LEGITIMATE_CONSTANT_P rejects |
| CONST_INTs that can't be loaded using simple insns. */ |
| if (TARGET_MIPS16) |
| return true; |
| |
| /* Otherwise check whether the constant can be loaded in a single |
| instruction. */ |
| return LUI_INT (op) || SMALL_INT (op) || SMALL_INT_UNSIGNED (op); |
| |
| case CONST: |
| case SYMBOL_REF: |
| case LABEL_REF: |
| if (CONST_GP_P (op)) |
| return true; |
| |
| return (mips_symbolic_constant_p (op, &symbol_type) |
| && !mips_split_p[symbol_type]); |
| |
| default: |
| return true; |
| } |
| } |
| |
| |
| /* Accept any operand that can appear in a mips16 constant table |
| instruction. We can't use any of the standard operand functions |
| because for these instructions we accept values that are not |
| accepted by LEGITIMATE_CONSTANT, such as arbitrary SYMBOL_REFs. */ |
| |
| int |
| consttable_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return CONSTANT_P (op); |
| } |
| |
| /* Return 1 if OP is a symbolic operand, i.e. a symbol_ref or a label_ref, |
| possibly with an offset. */ |
| |
| int |
| symbolic_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| enum mips_symbol_type symbol_type; |
| |
| return mips_symbolic_constant_p (op, &symbol_type); |
| } |
| |
| |
| /* Return true if we're generating PIC and OP is a global symbol. */ |
| |
| int |
| global_got_operand (rtx op, enum machine_mode mode) |
| { |
| enum mips_symbol_type symbol_type; |
| |
| return ((mode == VOIDmode || mode == GET_MODE (op)) |
| && mips_symbolic_constant_p (op, &symbol_type) |
| && symbol_type == SYMBOL_GOT_GLOBAL); |
| } |
| |
| |
| /* Likewise for local symbols. */ |
| |
| int |
| local_got_operand (rtx op, enum machine_mode mode) |
| { |
| enum mips_symbol_type symbol_type; |
| |
| return ((mode == VOIDmode || mode == GET_MODE (op)) |
| && mips_symbolic_constant_p (op, &symbol_type) |
| && symbol_type == SYMBOL_GOT_LOCAL); |
| } |
| |
| |
| /* Return true if OP is a memory reference that uses the stack pointer |
| as a base register. */ |
| |
| int |
| stack_operand (rtx op, enum machine_mode mode) |
| { |
| struct mips_address_info addr; |
| |
| return ((mode == VOIDmode || mode == GET_MODE (op)) |
| && GET_CODE (op) == MEM |
| && mips_classify_address (&addr, XEXP (op, 0), GET_MODE (op), false) |
| && addr.type == ADDRESS_REG |
| && addr.reg == stack_pointer_rtx); |
| } |
| |
| |
| /* 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. */ |
| |
| static 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 memory reference for the GOT slot whose offset is given by |
| mips_unspec_address (ADDR, SYMBOL_TYPE). Register BASE contains the |
| high part of the offset plus $gp. */ |
| |
| static rtx |
| mips_load_got (rtx base, rtx addr, enum mips_symbol_type symbol_type) |
| { |
| rtx mem, offset; |
| |
| offset = mips_unspec_address (addr, symbol_type); |
| mem = gen_rtx_MEM (ptr_mode, gen_rtx_LO_SUM (Pmode, base, offset)); |
| set_mem_alias_set (mem, mips_got_alias_set); |
| |
| /* GOT entries are constant and references to them can't trap. */ |
| RTX_UNCHANGING_P (mem) = 1; |
| MEM_NOTRAP_P (mem) = 1; |
| |
| return mem; |
| } |
| |
| |
| /* Return the offset of ADDR's GOT entry from _gp. ADDR is a |
| global_got_operand. */ |
| |
| rtx |
| mips_gotoff_global (rtx addr) |
| { |
| return mips_unspec_address (addr, SYMBOL_GOTOFF_GLOBAL); |
| } |
| |
| |
| /* Fetch the high part of local_got_operand ADDR from the GOT. */ |
| |
| rtx |
| mips_load_got_page (rtx addr) |
| { |
| return mips_load_got (pic_offset_table_rtx, addr, SYMBOL_GOTOFF_PAGE); |
| } |
| |
| |
| /* Fetch the address of global_got_operand ADDR from the GOT. BASE is a |
| register that holds the address _gp + %got_hi(ADDR). */ |
| |
| rtx |
| mips_load_got_global (rtx base, rtx addr) |
| { |
| return mips_load_got (base, addr, SYMBOL_GOTOFF_GLOBAL); |
| } |
| |
| |
| /* Return a legitimate address for REG + OFFSET. This function will |
| create a temporary register if OFFSET is not a SMALL_OPERAND. */ |
| |
| static rtx |
| mips_add_offset (rtx reg, HOST_WIDE_INT offset) |
| { |
| if (!SMALL_OPERAND (offset)) |
| reg = expand_simple_binop (GET_MODE (reg), PLUS, |
| GEN_INT (CONST_HIGH_PART (offset)), |
| reg, NULL, 0, OPTAB_WIDEN); |
| |
| return plus_constant (reg, CONST_LOW_PART (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 (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 = NIL; |
| 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 (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; |
| } |
| |
| /* The source of an SImode move must be a move_operand. Likewise |
| DImode moves on 64-bit targets. We need to deal with constants |
| that would be legitimate immediate_operands but not legitimate |
| move_operands. */ |
| if (GET_MODE_SIZE (mode) <= UNITS_PER_WORD |
| && 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); |
| } |
| |
| /* References to the string table on the mips16 only use a small |
| offset if the function is small. We can't check for LABEL_REF here, |
| because the offset is always large if the label is before the |
| referencing instruction. */ |
| |
| int |
| m16_usym8_4 (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| if (GET_CODE (op) == SYMBOL_REF |
| && SYMBOL_REF_FLAG (op) |
| && cfun->machine->insns_len > 0 |
| && (cfun->machine->insns_len + get_pool_size () + mips_string_length |
| < 4 * 0x100)) |
| { |
| struct string_constant *l; |
| |
| /* Make sure this symbol is on thelist of string constants to be |
| output for this function. It is possible that it has already |
| been output, in which case this requires a large offset. */ |
| for (l = string_constants; l != NULL; l = l->next) |
| if (strcmp (l->label, XSTR (op, 0)) == 0) |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| int |
| m16_usym5_4 (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| if (GET_CODE (op) == SYMBOL_REF |
| && SYMBOL_REF_FLAG (op) |
| && cfun->machine->insns_len > 0 |
| && (cfun->machine->insns_len + get_pool_size () + mips_string_length |
| < 4 * 0x20)) |
| { |
| struct string_constant *l; |
| |
| /* Make sure this symbol is on thelist of string constants to be |
| output for this function. It is possible that it has already |
| been output, in which case this requires a large offset. */ |
| for (l = string_constants; l != NULL; l = l->next) |
| if (strcmp (l->label, XSTR (op, 0)) == 0) |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| 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; |
| } |
| |
| /* 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 |
| *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) |
| *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_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_MIPS5400 || TUNE_MIPS5500) |
| *total = COSTS_N_INSNS ((mode == DImode) ? 4 : 3); |
| else if (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_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 |
| *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_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 a pseudo that points to the address of the current function. |
| The first time it is called for a function, an initializer for the |
| pseudo is emitted in the beginning of the function. */ |
| |
| rtx |
| embedded_pic_fnaddr_reg (void) |
| { |
| if (cfun->machine->embedded_pic_fnaddr_rtx == NULL) |
| { |
| rtx seq; |
| |
| cfun->machine->embedded_pic_fnaddr_rtx = gen_reg_rtx (Pmode); |
| |
| /* Output code at function start to initialize the pseudo-reg. */ |
| /* ??? We used to do this in FINALIZE_PIC, but that does not work for |
| inline functions, because it is called after RTL for the function |
| has been copied. The pseudo-reg in embedded_pic_fnaddr_rtx however |
| does not get copied, and ends up not matching the rest of the RTL. |
| This solution works, but means that we get unnecessary code to |
| initialize this value every time a function is inlined into another |
| function. */ |
| start_sequence (); |
| emit_insn (gen_get_fnaddr (cfun->machine->embedded_pic_fnaddr_rtx, |
| XEXP (DECL_RTL (current_function_decl), 0))); |
| seq = get_insns (); |
| end_sequence (); |
| push_topmost_sequence (); |
| emit_insn_after (seq, get_insns ()); |
| pop_topmost_sequence (); |
| } |
| |
| return cfun->machine->embedded_pic_fnaddr_rtx; |
| } |
| |
| /* Return RTL for the offset from the current function to the argument. |
| X is the symbol whose offset from the current function we want. */ |
| |
| rtx |
| embedded_pic_offset (rtx x) |
| { |
| /* Make sure it is emitted. */ |
| embedded_pic_fnaddr_reg (); |
| |
| return |
| gen_rtx_CONST (Pmode, |
| gen_rtx_MINUS (Pmode, x, |
| XEXP (DECL_RTL (current_function_decl), 0))); |
| } |
| |
| /* 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 (GET_CODE (op) == REG) |
| { |
| 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 (GET_CODE (op) == MEM) |
| 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) && GET_CODE (src) == MEM) |
| return false; |
| if (FP_REG_RTX_P (src) && GET_CODE (dest) == MEM) |
| 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 (GET_CODE (low_dest) == REG |
| && 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 (MD_REG_P (REGNO (src))) |
| return "mf%1\t%0"; |
| |
| 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 "li\t%0,%n1\n\tneg\t%0"; |
| } |
| |
| 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))) |
| 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; |
| } |
| abort (); |
| } |
| |
| /* Return an rtx for the gp save slot. Valid only when using o32 or |
| o64 abicalls. */ |
| |
| rtx |
| mips_gp_save_slot (void) |
| { |
| rtx loc; |
| |
| if (!TARGET_ABICALLS || TARGET_NEWABI) |
| abort (); |
| |
| if (frame_pointer_needed) |
| loc = hard_frame_pointer_rtx; |
| else |
| loc = stack_pointer_rtx; |
| loc = plus_constant (loc, current_function_outgoing_args_size); |
| loc = gen_rtx_MEM (Pmode, loc); |
| RTX_UNCHANGING_P (loc) = 1; |
| return loc; |
| } |
| |
| /* Make normal rtx_code into something we can index from an array */ |
| |
| static enum internal_test |
| map_test_to_internal_test (enum rtx_code test_code) |
| { |
| enum internal_test test = ITEST_MAX; |
| |
| switch (test_code) |
| { |
| case EQ: test = ITEST_EQ; break; |
| case NE: test = ITEST_NE; break; |
| case GT: test = ITEST_GT; break; |
| case GE: test = ITEST_GE; break; |
| case LT: test = ITEST_LT; break; |
| case LE: test = ITEST_LE; break; |
| case GTU: test = ITEST_GTU; break; |
| case GEU: test = ITEST_GEU; break; |
| case LTU: test = ITEST_LTU; break; |
| case LEU: test = ITEST_LEU; break; |
| default: break; |
| } |
| |
| return test; |
| } |
| |
| |
| /* Generate the code to compare two integer values. The return value is: |
| (reg:SI xx) The pseudo register the comparison is in |
| 0 No register, generate a simple branch. |
| |
| ??? This is called with result nonzero by the Scond patterns in |
| mips.md. These patterns are called with a target in the mode of |
| the Scond instruction pattern. Since this must be a constant, we |
| must use SImode. This means that if RESULT is nonzero, it will |
| always be an SImode register, even if TARGET_64BIT is true. We |
| cope with this by calling convert_move rather than emit_move_insn. |
| This will sometimes lead to an unnecessary extension of the result; |
| for example: |
| |
| long long |
| foo (long long i) |
| { |
| return i < 5; |
| } |
| |
| TEST_CODE is the rtx code for the comparison. |
| CMP0 and CMP1 are the two operands to compare. |
| RESULT is the register in which the result should be stored (null for |
| branches). |
| For branches, P_INVERT points to an integer that is nonzero on return |
| if the branch should be inverted. */ |
| |
| rtx |
| gen_int_relational (enum rtx_code test_code, rtx result, rtx cmp0, |
| rtx cmp1, int *p_invert) |
| { |
| struct cmp_info |
| { |
| enum rtx_code test_code; /* code to use in instruction (LT vs. LTU) */ |
| int const_low; /* low bound of constant we can accept */ |
| int const_high; /* high bound of constant we can accept */ |
| int const_add; /* constant to add (convert LE -> LT) */ |
| int reverse_regs; /* reverse registers in test */ |
| int invert_const; /* != 0 if invert value if cmp1 is constant */ |
| int invert_reg; /* != 0 if invert value if cmp1 is register */ |
| int unsignedp; /* != 0 for unsigned comparisons. */ |
| }; |
| |
| static const struct cmp_info info[ (int)ITEST_MAX ] = { |
| |
| { XOR, 0, 65535, 0, 0, 0, 0, 0 }, /* EQ */ |
| { XOR, 0, 65535, 0, 0, 1, 1, 0 }, /* NE */ |
| { LT, -32769, 32766, 1, 1, 1, 0, 0 }, /* GT */ |
| { LT, -32768, 32767, 0, 0, 1, 1, 0 }, /* GE */ |
| { LT, -32768, 32767, 0, 0, 0, 0, 0 }, /* LT */ |
| { LT, -32769, 32766, 1, 1, 0, 1, 0 }, /* LE */ |
| { LTU, -32769, 32766, 1, 1, 1, 0, 1 }, /* GTU */ |
| { LTU, -32768, 32767, 0, 0, 1, 1, 1 }, /* GEU */ |
| { LTU, -32768, 32767, 0, 0, 0, 0, 1 }, /* LTU */ |
| { LTU, -32769, 32766, 1, 1, 0, 1, 1 }, /* LEU */ |
| }; |
| |
| enum internal_test test; |
| enum machine_mode mode; |
| const struct cmp_info *p_info; |
| int branch_p; |
| int eqne_p; |
| int invert; |
| rtx reg; |
| rtx reg2; |
| |
| test = map_test_to_internal_test (test_code); |
| if (test == ITEST_MAX) |
| abort (); |
| |
| p_info = &info[(int) test]; |
| eqne_p = (p_info->test_code == XOR); |
| |
| mode = GET_MODE (cmp0); |
| if (mode == VOIDmode) |
| mode = GET_MODE (cmp1); |
| |
| /* Eliminate simple branches */ |
| branch_p = (result == 0); |
| if (branch_p) |
| { |
| if (GET_CODE (cmp0) == REG || GET_CODE (cmp0) == SUBREG) |
| { |
| /* Comparisons against zero are simple branches */ |
| if (GET_CODE (cmp1) == CONST_INT && INTVAL (cmp1) == 0 |
| && (! TARGET_MIPS16 || eqne_p)) |
| return 0; |
| |
| /* Test for beq/bne. */ |
| if (eqne_p && ! TARGET_MIPS16) |
| return 0; |
| } |
| |
| /* Allocate a pseudo to calculate the value in. */ |
| result = gen_reg_rtx (mode); |
| } |
| |
| /* Make sure we can handle any constants given to us. */ |
| if (GET_CODE (cmp0) == CONST_INT) |
| cmp0 = force_reg (mode, cmp0); |
| |
| if (GET_CODE (cmp1) == CONST_INT) |
| { |
| HOST_WIDE_INT value = INTVAL (cmp1); |
| |
| if (value < p_info->const_low |
| || value > p_info->const_high |
| /* ??? Why? And why wasn't the similar code below modified too? */ |
| || (TARGET_64BIT |
| && HOST_BITS_PER_WIDE_INT < 64 |
| && p_info->const_add != 0 |
| && ((p_info->unsignedp |
| ? ((unsigned HOST_WIDE_INT) (value + p_info->const_add) |
| > (unsigned HOST_WIDE_INT) INTVAL (cmp1)) |
| : (value + p_info->const_add) > INTVAL (cmp1)) |
| != (p_info->const_add > 0)))) |
| cmp1 = force_reg (mode, cmp1); |
| } |
| |
| /* See if we need to invert the result. */ |
| invert = (GET_CODE (cmp1) == CONST_INT |
| ? p_info->invert_const : p_info->invert_reg); |
| |
| if (p_invert != (int *)0) |
| { |
| *p_invert = invert; |
| invert = 0; |
| } |
| |
| /* Comparison to constants, may involve adding 1 to change a LT into LE. |
| Comparison between two registers, may involve switching operands. */ |
| if (GET_CODE (cmp1) == CONST_INT) |
| { |
| if (p_info->const_add != 0) |
| { |
| HOST_WIDE_INT new = INTVAL (cmp1) + p_info->const_add; |
| |
| /* If modification of cmp1 caused overflow, |
| we would get the wrong answer if we follow the usual path; |
| thus, x > 0xffffffffU would turn into x > 0U. */ |
| if ((p_info->unsignedp |
| ? (unsigned HOST_WIDE_INT) new > |
| (unsigned HOST_WIDE_INT) INTVAL (cmp1) |
| : new > INTVAL (cmp1)) |
| != (p_info->const_add > 0)) |
| { |
| /* This test is always true, but if INVERT is true then |
| the result of the test needs to be inverted so 0 should |
| be returned instead. */ |
| emit_move_insn (result, invert ? const0_rtx : const_true_rtx); |
| return result; |
| } |
| else |
| cmp1 = GEN_INT (new); |
| } |
| } |
| |
| else if (p_info->reverse_regs) |
| { |
| rtx temp = cmp0; |
| cmp0 = cmp1; |
| cmp1 = temp; |
| } |
| |
| if (test == ITEST_NE && GET_CODE (cmp1) == CONST_INT && INTVAL (cmp1) == 0) |
| reg = cmp0; |
| else |
| { |
| reg = (invert || eqne_p) ? gen_reg_rtx (mode) : result; |
| convert_move (reg, gen_rtx (p_info->test_code, mode, cmp0, cmp1), 0); |
| } |
| |
| if (test == ITEST_NE) |
| { |
| if (! TARGET_MIPS16) |
| { |
| convert_move (result, gen_rtx (GTU, mode, reg, const0_rtx), 0); |
| if (p_invert != NULL) |
| *p_invert = 0; |
| invert = 0; |
| } |
| else |
| { |
| reg2 = invert ? gen_reg_rtx (mode) : result; |
| convert_move (reg2, gen_rtx (LTU, mode, reg, const1_rtx), 0); |
| reg = reg2; |
| } |
| } |
| |
| else if (test == ITEST_EQ) |
| { |
| reg2 = invert ? gen_reg_rtx (mode) : result; |
| convert_move (reg2, gen_rtx_LTU (mode, reg, const1_rtx), 0); |
| reg = reg2; |
| } |
| |
| if (invert) |
| { |
| rtx one; |
| |
| if (! TARGET_MIPS16) |
| one = const1_rtx; |
| else |
| { |
| /* The value is in $24. Copy it to another register, so |
| that reload doesn't think it needs to store the $24 and |
| the input to the XOR in the same location. */ |
| reg2 = gen_reg_rtx (mode); |
| emit_move_insn (reg2, reg); |
| reg = reg2; |
| one = force_reg (mode, const1_rtx); |
| } |
| convert_move (result, gen_rtx (XOR, mode, reg, one), 0); |
| } |
| |
| return result; |
| } |
| |
| /* Work out how to check a floating-point condition. We need a |
| separate comparison instruction (C.cond.fmt), followed by a |
| branch or conditional move. Given that IN_CODE is the |
| required condition, set *CMP_CODE to the C.cond.fmt code |
| and *action_code to the branch or move code. */ |
| |
| static void |
| get_float_compare_codes (enum rtx_code in_code, enum rtx_code *cmp_code, |
| enum rtx_code *action_code) |
| { |
| switch (in_code) |
| { |
| case NE: |
| case UNGE: |
| case UNGT: |
| case LTGT: |
| case ORDERED: |
| *cmp_code = reverse_condition_maybe_unordered (in_code); |
| *action_code = EQ; |
| break; |
| |
| default: |
| *cmp_code = in_code; |
| *action_code = NE; |
| break; |
| } |
| } |
| |
| /* 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 test_code) |
| { |
| enum cmp_type type = branch_type; |
| rtx cmp0 = branch_cmp[0]; |
| rtx cmp1 = branch_cmp[1]; |
| enum machine_mode mode; |
| enum rtx_code cmp_code; |
| rtx reg; |
| int invert; |
| rtx label1, label2; |
| |
| switch (type) |
| { |
| case CMP_SI: |
| case CMP_DI: |
| mode = type == CMP_SI ? SImode : DImode; |
| invert = 0; |
| reg = gen_int_relational (test_code, NULL_RTX, cmp0, cmp1, &invert); |
| |
| if (reg) |
| { |
| cmp0 = reg; |
| cmp1 = const0_rtx; |
| test_code = NE; |
| } |
| else if (GET_CODE (cmp1) == CONST_INT && INTVAL (cmp1) != 0) |
| /* We don't want to build a comparison against a nonzero |
| constant. */ |
| cmp1 = force_reg (mode, cmp1); |
| |
| break; |
| |
| case CMP_SF: |
| case CMP_DF: |
| if (! ISA_HAS_8CC) |
| reg = gen_rtx_REG (CCmode, FPSW_REGNUM); |
| else |
| reg = gen_reg_rtx (CCmode); |
| |
| get_float_compare_codes (test_code, &cmp_code, &test_code); |
| emit_insn (gen_rtx_SET (VOIDmode, reg, |
| gen_rtx (cmp_code, CCmode, cmp0, cmp1))); |
| |
| mode = CCmode; |
| cmp0 = reg; |
| cmp1 = const0_rtx; |
| invert = 0; |
| break; |
| |
| default: |
| fatal_insn ("bad test", gen_rtx (test_code, VOIDmode, cmp0, cmp1)); |
| } |
| |
| /* Generate the branch. */ |
| |
| label1 = gen_rtx_LABEL_REF (VOIDmode, operands[0]); |
| label2 = pc_rtx; |
| |
| if (invert) |
| { |
| label2 = label1; |
| label1 = pc_rtx; |
| } |
| |
| emit_jump_insn (gen_rtx_SET (VOIDmode, pc_rtx, |
| gen_rtx_IF_THEN_ELSE (VOIDmode, |
| gen_rtx (test_code, mode, |
| cmp0, cmp1), |
| label1, label2))); |
| } |
| |
| /* 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) |
| { |
| rtx op0 = branch_cmp[0]; |
| rtx op1 = branch_cmp[1]; |
| enum machine_mode mode = GET_MODE (branch_cmp[0]); |
| enum rtx_code cmp_code = GET_CODE (operands[ |