| /* Subroutines used for MIPS code generation. |
| Copyright (C) 1989-2021 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 3, or (at your option) |
| any later version. |
| |
| GCC is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GCC; see the file COPYING3. If not see |
| <http://www.gnu.org/licenses/>. */ |
| |
| #define IN_TARGET_CODE 1 |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "backend.h" |
| #include "target.h" |
| #include "rtl.h" |
| #include "tree.h" |
| #include "memmodel.h" |
| #include "gimple.h" |
| #include "cfghooks.h" |
| #include "df.h" |
| #include "tm_p.h" |
| #include "stringpool.h" |
| #include "attribs.h" |
| #include "optabs.h" |
| #include "regs.h" |
| #include "emit-rtl.h" |
| #include "recog.h" |
| #include "cgraph.h" |
| #include "diagnostic.h" |
| #include "insn-attr.h" |
| #include "output.h" |
| #include "alias.h" |
| #include "fold-const.h" |
| #include "varasm.h" |
| #include "stor-layout.h" |
| #include "calls.h" |
| #include "explow.h" |
| #include "expr.h" |
| #include "libfuncs.h" |
| #include "reload.h" |
| #include "common/common-target.h" |
| #include "langhooks.h" |
| #include "cfgrtl.h" |
| #include "cfganal.h" |
| #include "sched-int.h" |
| #include "gimplify.h" |
| #include "target-globals.h" |
| #include "tree-pass.h" |
| #include "context.h" |
| #include "builtins.h" |
| #include "rtl-iter.h" |
| #include "flags.h" |
| |
| /* This file should be included last. */ |
| #include "target-def.h" |
| |
| /* True if X is an UNSPEC wrapper around a SYMBOL_REF or LABEL_REF. */ |
| #define UNSPEC_ADDRESS_P(X) \ |
| (GET_CODE (X) == UNSPEC \ |
| && XINT (X, 1) >= UNSPEC_ADDRESS_FIRST \ |
| && XINT (X, 1) < UNSPEC_ADDRESS_FIRST + NUM_SYMBOL_TYPES) |
| |
| /* Extract the symbol or label from UNSPEC wrapper X. */ |
| #define UNSPEC_ADDRESS(X) \ |
| XVECEXP (X, 0, 0) |
| |
| /* Extract the symbol type from UNSPEC wrapper X. */ |
| #define UNSPEC_ADDRESS_TYPE(X) \ |
| ((enum mips_symbol_type) (XINT (X, 1) - UNSPEC_ADDRESS_FIRST)) |
| |
| /* The maximum distance between the top of the stack frame and the |
| value $sp has when we save and restore registers. |
| |
| The value for normal-mode code must be a SMALL_OPERAND and must |
| preserve the maximum stack alignment. We therefore use a value |
| of 0x7ff0 in this case. |
| |
| microMIPS LWM and SWM support 12-bit offsets (from -0x800 to 0x7ff), |
| so we use a maximum of 0x7f0 for TARGET_MICROMIPS. |
| |
| MIPS16e SAVE and RESTORE instructions can adjust the stack pointer by |
| up to 0x7f8 bytes and can usually save or restore all the registers |
| that we need to save or restore. (Note that we can only use these |
| instructions for o32, for which the stack alignment is 8 bytes.) |
| |
| We use a maximum gap of 0x100 or 0x400 for MIPS16 code when SAVE and |
| RESTORE are not available. We can then use unextended instructions |
| to save and restore registers, and to allocate and deallocate the top |
| part of the frame. */ |
| #define MIPS_MAX_FIRST_STACK_STEP \ |
| (!TARGET_COMPRESSION ? 0x7ff0 \ |
| : TARGET_MICROMIPS || GENERATE_MIPS16E_SAVE_RESTORE ? 0x7f8 \ |
| : TARGET_64BIT ? 0x100 : 0x400) |
| |
| /* True if INSN is a mips.md pattern or asm statement. */ |
| /* ??? This test exists through the compiler, perhaps it should be |
| moved to rtl.h. */ |
| #define USEFUL_INSN_P(INSN) \ |
| (NONDEBUG_INSN_P (INSN) \ |
| && GET_CODE (PATTERN (INSN)) != USE \ |
| && GET_CODE (PATTERN (INSN)) != CLOBBER) |
| |
| /* If INSN is a delayed branch sequence, return the first instruction |
| in the sequence, otherwise return INSN itself. */ |
| #define SEQ_BEGIN(INSN) \ |
| (INSN_P (INSN) && GET_CODE (PATTERN (INSN)) == SEQUENCE \ |
| ? as_a <rtx_insn *> (XVECEXP (PATTERN (INSN), 0, 0)) \ |
| : (INSN)) |
| |
| /* Likewise for the last instruction in a delayed branch sequence. */ |
| #define SEQ_END(INSN) \ |
| (INSN_P (INSN) && GET_CODE (PATTERN (INSN)) == SEQUENCE \ |
| ? as_a <rtx_insn *> (XVECEXP (PATTERN (INSN), \ |
| 0, \ |
| XVECLEN (PATTERN (INSN), 0) - 1)) \ |
| : (INSN)) |
| |
| /* Execute the following loop body with SUBINSN set to each instruction |
| between SEQ_BEGIN (INSN) and SEQ_END (INSN) inclusive. */ |
| #define FOR_EACH_SUBINSN(SUBINSN, INSN) \ |
| for ((SUBINSN) = SEQ_BEGIN (INSN); \ |
| (SUBINSN) != NEXT_INSN (SEQ_END (INSN)); \ |
| (SUBINSN) = NEXT_INSN (SUBINSN)) |
| |
| /* True if bit BIT is set in VALUE. */ |
| #define BITSET_P(VALUE, BIT) (((VALUE) & (1 << (BIT))) != 0) |
| |
| /* Return the opcode for a ptr_mode load of the form: |
| |
| l[wd] DEST, OFFSET(BASE). */ |
| #define MIPS_LOAD_PTR(DEST, OFFSET, BASE) \ |
| (((ptr_mode == DImode ? 0x37 : 0x23) << 26) \ |
| | ((BASE) << 21) \ |
| | ((DEST) << 16) \ |
| | (OFFSET)) |
| |
| /* Return the opcode to move register SRC into register DEST. */ |
| #define MIPS_MOVE(DEST, SRC) \ |
| ((TARGET_64BIT ? 0x2d : 0x21) \ |
| | ((DEST) << 11) \ |
| | ((SRC) << 21)) |
| |
| /* Return the opcode for: |
| |
| lui DEST, VALUE. */ |
| #define MIPS_LUI(DEST, VALUE) \ |
| ((0xf << 26) | ((DEST) << 16) | (VALUE)) |
| |
| /* Return the opcode to jump to register DEST. When the JR opcode is not |
| available use JALR $0, DEST. */ |
| #define MIPS_JR(DEST) \ |
| (TARGET_CB_ALWAYS ? ((0x1b << 27) | ((DEST) << 16)) \ |
| : (((DEST) << 21) | (ISA_HAS_JR ? 0x8 : 0x9))) |
| |
| /* Return the opcode for: |
| |
| bal . + (1 + OFFSET) * 4. */ |
| #define MIPS_BAL(OFFSET) \ |
| ((0x1 << 26) | (0x11 << 16) | (OFFSET)) |
| |
| /* Return the usual opcode for a nop. */ |
| #define MIPS_NOP 0 |
| |
| /* 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. */ |
| enum mips_address_type { |
| ADDRESS_REG, |
| ADDRESS_LO_SUM, |
| ADDRESS_CONST_INT, |
| ADDRESS_SYMBOLIC |
| }; |
| |
| /* Classifies an unconditional branch of interest for the P6600. */ |
| |
| enum mips_ucbranch_type |
| { |
| /* May not even be a branch. */ |
| UC_UNDEFINED, |
| UC_BALC, |
| UC_OTHER |
| }; |
| |
| /* Macros to create an enumeration identifier for a function prototype. */ |
| #define MIPS_FTYPE_NAME1(A, B) MIPS_##A##_FTYPE_##B |
| #define MIPS_FTYPE_NAME2(A, B, C) MIPS_##A##_FTYPE_##B##_##C |
| #define MIPS_FTYPE_NAME3(A, B, C, D) MIPS_##A##_FTYPE_##B##_##C##_##D |
| #define MIPS_FTYPE_NAME4(A, B, C, D, E) MIPS_##A##_FTYPE_##B##_##C##_##D##_##E |
| |
| /* Classifies the prototype of a built-in function. */ |
| enum mips_function_type { |
| #define DEF_MIPS_FTYPE(NARGS, LIST) MIPS_FTYPE_NAME##NARGS LIST, |
| #include "config/mips/mips-ftypes.def" |
| #undef DEF_MIPS_FTYPE |
| MIPS_MAX_FTYPE_MAX |
| }; |
| |
| /* Specifies how a built-in function should be converted into rtl. */ |
| enum mips_builtin_type { |
| /* The function corresponds directly to an .md pattern. The return |
| value is mapped to operand 0 and the arguments are mapped to |
| operands 1 and above. */ |
| MIPS_BUILTIN_DIRECT, |
| |
| /* The function corresponds directly to an .md pattern. There is no return |
| value and the arguments are mapped to operands 0 and above. */ |
| MIPS_BUILTIN_DIRECT_NO_TARGET, |
| |
| /* The function corresponds to a comparison instruction followed by |
| a mips_cond_move_tf_ps pattern. The first two arguments are the |
| values to compare and the second two arguments are the vector |
| operands for the movt.ps or movf.ps instruction (in assembly order). */ |
| MIPS_BUILTIN_MOVF, |
| MIPS_BUILTIN_MOVT, |
| |
| /* The function corresponds to a V2SF comparison instruction. Operand 0 |
| of this instruction is the result of the comparison, which has mode |
| CCV2 or CCV4. The function arguments are mapped to operands 1 and |
| above. The function's return value is an SImode boolean that is |
| true under the following conditions: |
| |
| MIPS_BUILTIN_CMP_ANY: one of the registers is true |
| MIPS_BUILTIN_CMP_ALL: all of the registers are true |
| MIPS_BUILTIN_CMP_LOWER: the first register is true |
| MIPS_BUILTIN_CMP_UPPER: the second register is true. */ |
| MIPS_BUILTIN_CMP_ANY, |
| MIPS_BUILTIN_CMP_ALL, |
| MIPS_BUILTIN_CMP_UPPER, |
| MIPS_BUILTIN_CMP_LOWER, |
| |
| /* As above, but the instruction only sets a single $fcc register. */ |
| MIPS_BUILTIN_CMP_SINGLE, |
| |
| /* The function corresponds to an MSA conditional branch instruction |
| combined with a compare instruction. */ |
| MIPS_BUILTIN_MSA_TEST_BRANCH, |
| |
| /* For generating bposge32 branch instructions in MIPS32 DSP ASE. */ |
| MIPS_BUILTIN_BPOSGE32 |
| }; |
| |
| /* Invoke MACRO (COND) for each C.cond.fmt condition. */ |
| #define MIPS_FP_CONDITIONS(MACRO) \ |
| MACRO (f), \ |
| MACRO (un), \ |
| MACRO (eq), \ |
| MACRO (ueq), \ |
| MACRO (olt), \ |
| MACRO (ult), \ |
| MACRO (ole), \ |
| MACRO (ule), \ |
| MACRO (sf), \ |
| MACRO (ngle), \ |
| MACRO (seq), \ |
| MACRO (ngl), \ |
| MACRO (lt), \ |
| MACRO (nge), \ |
| MACRO (le), \ |
| MACRO (ngt) |
| |
| /* Enumerates the codes above as MIPS_FP_COND_<X>. */ |
| #define DECLARE_MIPS_COND(X) MIPS_FP_COND_ ## X |
| enum mips_fp_condition { |
| MIPS_FP_CONDITIONS (DECLARE_MIPS_COND) |
| }; |
| #undef DECLARE_MIPS_COND |
| |
| /* Index X provides the string representation of MIPS_FP_COND_<X>. */ |
| #define STRINGIFY(X) #X |
| static const char *const mips_fp_conditions[] = { |
| MIPS_FP_CONDITIONS (STRINGIFY) |
| }; |
| #undef STRINGIFY |
| |
| /* A class used to control a comdat-style stub that we output in each |
| translation unit that needs it. */ |
| class mips_one_only_stub { |
| public: |
| virtual ~mips_one_only_stub () {} |
| |
| /* Return the name of the stub. */ |
| virtual const char *get_name () = 0; |
| |
| /* Output the body of the function to asm_out_file. */ |
| virtual void output_body () = 0; |
| }; |
| |
| /* Tuning information that is automatically derived from other sources |
| (such as the scheduler). */ |
| static struct { |
| /* The architecture and tuning settings that this structure describes. */ |
| enum processor arch; |
| enum processor tune; |
| |
| /* True if this structure describes MIPS16 settings. */ |
| bool mips16_p; |
| |
| /* True if the structure has been initialized. */ |
| bool initialized_p; |
| |
| /* True if "MULT $0, $0" is preferable to "MTLO $0; MTHI $0" |
| when optimizing for speed. */ |
| bool fast_mult_zero_zero_p; |
| } mips_tuning_info; |
| |
| /* Information about a single argument. */ |
| struct mips_arg_info { |
| /* True if the argument is passed in a floating-point register, or |
| would have been if we hadn't run out of registers. */ |
| bool fpr_p; |
| |
| /* The number of words passed in registers, rounded up. */ |
| unsigned int reg_words; |
| |
| /* For EABI, the offset of the first register from GP_ARG_FIRST or |
| FP_ARG_FIRST. For other ABIs, the offset of the first register from |
| the start of the ABI's argument structure (see the CUMULATIVE_ARGS |
| comment for details). |
| |
| The value is MAX_ARGS_IN_REGISTERS if the argument is passed entirely |
| on the stack. */ |
| unsigned int reg_offset; |
| |
| /* The number of words that must be passed on the stack, rounded up. */ |
| unsigned int stack_words; |
| |
| /* The offset from the start of the stack overflow area of the argument's |
| first stack word. Only meaningful when STACK_WORDS is nonzero. */ |
| unsigned int stack_offset; |
| }; |
| |
| /* Information about an address described by mips_address_type. |
| |
| ADDRESS_CONST_INT |
| No fields are used. |
| |
| ADDRESS_REG |
| REG is the base register and OFFSET is the constant offset. |
| |
| ADDRESS_LO_SUM |
| REG and OFFSET are the operands to the LO_SUM and SYMBOL_TYPE |
| is the type of symbol it references. |
| |
| ADDRESS_SYMBOLIC |
| SYMBOL_TYPE is the type of symbol that the address references. */ |
| 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. CODE[0] is undefined. */ |
| 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 |
| |
| /* Information about a MIPS16e SAVE or RESTORE instruction. */ |
| struct mips16e_save_restore_info { |
| /* The number of argument registers saved by a SAVE instruction. |
| 0 for RESTORE instructions. */ |
| unsigned int nargs; |
| |
| /* Bit X is set if the instruction saves or restores GPR X. */ |
| unsigned int mask; |
| |
| /* The total number of bytes to allocate. */ |
| HOST_WIDE_INT size; |
| }; |
| |
| /* Costs of various operations on the different architectures. */ |
| |
| struct mips_rtx_cost_data |
| { |
| unsigned short fp_add; |
| unsigned short fp_mult_sf; |
| unsigned short fp_mult_df; |
| unsigned short fp_div_sf; |
| unsigned short fp_div_df; |
| unsigned short int_mult_si; |
| unsigned short int_mult_di; |
| unsigned short int_div_si; |
| unsigned short int_div_di; |
| unsigned short branch_cost; |
| unsigned short memory_latency; |
| }; |
| |
| /* Global variables for machine-dependent things. */ |
| |
| /* The -G setting, or the configuration's default small-data limit if |
| no -G option is given. */ |
| static unsigned int mips_small_data_threshold; |
| |
| /* The number of file directives written by mips_output_filename. */ |
| int num_source_filenames; |
| |
| /* The name that appeared in the last .file directive written by |
| mips_output_filename, or "" if mips_output_filename hasn't |
| written anything yet. */ |
| const char *current_function_file = ""; |
| |
| /* Arrays that map GCC register numbers to debugger register numbers. */ |
| int mips_dbx_regno[FIRST_PSEUDO_REGISTER]; |
| int mips_dwarf_regno[FIRST_PSEUDO_REGISTER]; |
| |
| /* Information about the current function's epilogue, used only while |
| expanding it. */ |
| static struct { |
| /* A list of queued REG_CFA_RESTORE notes. */ |
| rtx cfa_restores; |
| |
| /* The CFA is currently defined as CFA_REG + CFA_OFFSET. */ |
| rtx cfa_reg; |
| HOST_WIDE_INT cfa_offset; |
| |
| /* The offset of the CFA from the stack pointer while restoring |
| registers. */ |
| HOST_WIDE_INT cfa_restore_sp_offset; |
| } mips_epilogue; |
| |
| /* The nesting depth of the PRINT_OPERAND '%(', '%<' and '%[' constructs. */ |
| struct mips_asm_switch mips_noreorder = { "reorder", 0 }; |
| struct mips_asm_switch mips_nomacro = { "macro", 0 }; |
| struct mips_asm_switch mips_noat = { "at", 0 }; |
| |
| /* True if we're writing out a branch-likely instruction rather than a |
| normal branch. */ |
| static bool mips_branch_likely; |
| |
| /* The current instruction-set architecture. */ |
| enum processor mips_arch; |
| const struct mips_cpu_info *mips_arch_info; |
| |
| /* The processor that we should tune the code for. */ |
| enum processor mips_tune; |
| const struct mips_cpu_info *mips_tune_info; |
| |
| /* The ISA level associated with mips_arch. */ |
| int mips_isa; |
| |
| /* The ISA revision level. This is 0 for MIPS I to V and N for |
| MIPS{32,64}rN. */ |
| int mips_isa_rev; |
| |
| /* The architecture selected by -mipsN, or null if -mipsN wasn't used. */ |
| static const struct mips_cpu_info *mips_isa_option_info; |
| |
| /* Which cost information to use. */ |
| static const struct mips_rtx_cost_data *mips_cost; |
| |
| /* The ambient target flags, excluding MASK_MIPS16. */ |
| static int mips_base_target_flags; |
| |
| /* The default compression mode. */ |
| unsigned int mips_base_compression_flags; |
| |
| /* The ambient values of other global variables. */ |
| static int mips_base_schedule_insns; /* flag_schedule_insns */ |
| static int mips_base_reorder_blocks_and_partition; /* flag_reorder... */ |
| static int mips_base_move_loop_invariants; /* flag_move_loop_invariants */ |
| static const char *mips_base_align_loops; /* align_loops */ |
| static const char *mips_base_align_jumps; /* align_jumps */ |
| static const char *mips_base_align_functions; /* align_functions */ |
| |
| /* Index [M][R] is true if register R is allowed to hold a value of mode M. */ |
| static bool mips_hard_regno_mode_ok_p[MAX_MACHINE_MODE][FIRST_PSEUDO_REGISTER]; |
| |
| /* Index C is true if character C is a valid PRINT_OPERAND punctation |
| character. */ |
| static bool mips_print_operand_punct[256]; |
| |
| 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. */ |
| bool mips_split_p[NUM_SYMBOL_TYPES]; |
| |
| /* mips_split_hi_p[X] is true if the high parts of symbols of type X |
| can be split by mips_split_symbol. */ |
| bool mips_split_hi_p[NUM_SYMBOL_TYPES]; |
| |
| /* mips_use_pcrel_pool_p[X] is true if symbols of type X should be |
| forced into a PC-relative constant pool. */ |
| bool mips_use_pcrel_pool_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. */ |
| const char *mips_lo_relocs[NUM_SYMBOL_TYPES]; |
| |
| /* Likewise for HIGHs. */ |
| const char *mips_hi_relocs[NUM_SYMBOL_TYPES]; |
| |
| /* Target state for MIPS16. */ |
| struct target_globals *mips16_globals; |
| |
| /* Target state for MICROMIPS. */ |
| struct target_globals *micromips_globals; |
| |
| /* Cached value of can_issue_more. This is cached in mips_variable_issue hook |
| and returned from mips_sched_reorder2. */ |
| static int cached_can_issue_more; |
| |
| /* The stubs for various MIPS16 support functions, if used. */ |
| static mips_one_only_stub *mips16_rdhwr_stub; |
| static mips_one_only_stub *mips16_get_fcsr_stub; |
| static mips_one_only_stub *mips16_set_fcsr_stub; |
| |
| /* Index R is the smallest register class that contains register R. */ |
| const enum reg_class mips_regno_to_class[FIRST_PSEUDO_REGISTER] = { |
| LEA_REGS, LEA_REGS, M16_STORE_REGS, V1_REG, |
| M16_STORE_REGS, M16_STORE_REGS, M16_STORE_REGS, M16_STORE_REGS, |
| LEA_REGS, LEA_REGS, LEA_REGS, LEA_REGS, |
| LEA_REGS, LEA_REGS, LEA_REGS, LEA_REGS, |
| M16_REGS, M16_STORE_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, M16_SP_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, |
| MD0_REG, MD1_REG, NO_REGS, ST_REGS, |
| ST_REGS, ST_REGS, ST_REGS, ST_REGS, |
| ST_REGS, ST_REGS, ST_REGS, NO_REGS, |
| NO_REGS, FRAME_REGS, FRAME_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, |
| DSP_ACC_REGS, DSP_ACC_REGS, DSP_ACC_REGS, DSP_ACC_REGS, |
| DSP_ACC_REGS, DSP_ACC_REGS, ALL_REGS, ALL_REGS, |
| ALL_REGS, ALL_REGS, ALL_REGS, ALL_REGS |
| }; |
| |
| static tree mips_handle_interrupt_attr (tree *, tree, tree, int, bool *); |
| static tree mips_handle_use_shadow_register_set_attr (tree *, tree, tree, int, |
| bool *); |
| |
| /* The value of TARGET_ATTRIBUTE_TABLE. */ |
| static const struct attribute_spec mips_attribute_table[] = { |
| /* { name, min_len, max_len, decl_req, type_req, fn_type_req, |
| affects_type_identity, handler, exclude } */ |
| { "long_call", 0, 0, false, true, true, false, NULL, NULL }, |
| { "short_call", 0, 0, false, true, true, false, NULL, NULL }, |
| { "far", 0, 0, false, true, true, false, NULL, NULL }, |
| { "near", 0, 0, false, true, true, false, NULL, NULL }, |
| /* We would really like to treat "mips16" and "nomips16" as type |
| attributes, but GCC doesn't provide the hooks we need to support |
| the right conversion rules. As declaration attributes, they affect |
| code generation but don't carry other semantics. */ |
| { "mips16", 0, 0, true, false, false, false, NULL, NULL }, |
| { "nomips16", 0, 0, true, false, false, false, NULL, NULL }, |
| { "micromips", 0, 0, true, false, false, false, NULL, NULL }, |
| { "nomicromips", 0, 0, true, false, false, false, NULL, NULL }, |
| { "nocompression", 0, 0, true, false, false, false, NULL, NULL }, |
| /* Allow functions to be specified as interrupt handlers */ |
| { "interrupt", 0, 1, false, true, true, false, mips_handle_interrupt_attr, |
| NULL }, |
| { "use_shadow_register_set", 0, 1, false, true, true, false, |
| mips_handle_use_shadow_register_set_attr, NULL }, |
| { "keep_interrupts_masked", 0, 0, false, true, true, false, NULL, NULL }, |
| { "use_debug_exception_return", 0, 0, false, true, true, false, NULL, NULL }, |
| { NULL, 0, 0, false, false, false, false, NULL, NULL } |
| }; |
| |
| /* A table describing all the processors GCC knows about; see |
| mips-cpus.def for details. */ |
| static const struct mips_cpu_info mips_cpu_info_table[] = { |
| #define MIPS_CPU(NAME, CPU, ISA, FLAGS) \ |
| { NAME, CPU, ISA, FLAGS }, |
| #include "mips-cpus.def" |
| #undef MIPS_CPU |
| }; |
| |
| /* Default costs. If these are used for a processor we should look |
| up the actual costs. */ |
| #define DEFAULT_COSTS COSTS_N_INSNS (6), /* fp_add */ \ |
| COSTS_N_INSNS (7), /* fp_mult_sf */ \ |
| COSTS_N_INSNS (8), /* fp_mult_df */ \ |
| COSTS_N_INSNS (23), /* fp_div_sf */ \ |
| COSTS_N_INSNS (36), /* fp_div_df */ \ |
| COSTS_N_INSNS (10), /* int_mult_si */ \ |
| COSTS_N_INSNS (10), /* int_mult_di */ \ |
| COSTS_N_INSNS (69), /* int_div_si */ \ |
| COSTS_N_INSNS (69), /* int_div_di */ \ |
| 2, /* branch_cost */ \ |
| 4 /* memory_latency */ |
| |
| /* Floating-point costs for processors without an FPU. Just assume that |
| all floating-point libcalls are very expensive. */ |
| #define SOFT_FP_COSTS COSTS_N_INSNS (256), /* fp_add */ \ |
| COSTS_N_INSNS (256), /* fp_mult_sf */ \ |
| COSTS_N_INSNS (256), /* fp_mult_df */ \ |
| COSTS_N_INSNS (256), /* fp_div_sf */ \ |
| COSTS_N_INSNS (256) /* fp_div_df */ |
| |
| /* Costs to use when optimizing for size. */ |
| static const struct mips_rtx_cost_data mips_rtx_cost_optimize_size = { |
| COSTS_N_INSNS (1), /* fp_add */ |
| COSTS_N_INSNS (1), /* fp_mult_sf */ |
| COSTS_N_INSNS (1), /* fp_mult_df */ |
| COSTS_N_INSNS (1), /* fp_div_sf */ |
| COSTS_N_INSNS (1), /* fp_div_df */ |
| COSTS_N_INSNS (1), /* int_mult_si */ |
| COSTS_N_INSNS (1), /* int_mult_di */ |
| COSTS_N_INSNS (1), /* int_div_si */ |
| COSTS_N_INSNS (1), /* int_div_di */ |
| 2, /* branch_cost */ |
| 4 /* memory_latency */ |
| }; |
| |
| /* Costs to use when optimizing for speed, indexed by processor. */ |
| static const struct mips_rtx_cost_data |
| mips_rtx_cost_data[NUM_PROCESSOR_VALUES] = { |
| { /* R3000 */ |
| COSTS_N_INSNS (2), /* fp_add */ |
| COSTS_N_INSNS (4), /* fp_mult_sf */ |
| COSTS_N_INSNS (5), /* fp_mult_df */ |
| COSTS_N_INSNS (12), /* fp_div_sf */ |
| COSTS_N_INSNS (19), /* fp_div_df */ |
| COSTS_N_INSNS (12), /* int_mult_si */ |
| COSTS_N_INSNS (12), /* int_mult_di */ |
| COSTS_N_INSNS (35), /* int_div_si */ |
| COSTS_N_INSNS (35), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* 4KC */ |
| SOFT_FP_COSTS, |
| COSTS_N_INSNS (6), /* int_mult_si */ |
| COSTS_N_INSNS (6), /* int_mult_di */ |
| COSTS_N_INSNS (36), /* int_div_si */ |
| COSTS_N_INSNS (36), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* 4KP */ |
| SOFT_FP_COSTS, |
| COSTS_N_INSNS (36), /* int_mult_si */ |
| COSTS_N_INSNS (36), /* int_mult_di */ |
| COSTS_N_INSNS (37), /* int_div_si */ |
| COSTS_N_INSNS (37), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* 5KC */ |
| SOFT_FP_COSTS, |
| COSTS_N_INSNS (4), /* int_mult_si */ |
| COSTS_N_INSNS (11), /* int_mult_di */ |
| COSTS_N_INSNS (36), /* int_div_si */ |
| COSTS_N_INSNS (68), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* 5KF */ |
| COSTS_N_INSNS (4), /* fp_add */ |
| COSTS_N_INSNS (4), /* fp_mult_sf */ |
| COSTS_N_INSNS (5), /* fp_mult_df */ |
| COSTS_N_INSNS (17), /* fp_div_sf */ |
| COSTS_N_INSNS (32), /* fp_div_df */ |
| COSTS_N_INSNS (4), /* int_mult_si */ |
| COSTS_N_INSNS (11), /* int_mult_di */ |
| COSTS_N_INSNS (36), /* int_div_si */ |
| COSTS_N_INSNS (68), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* 20KC */ |
| COSTS_N_INSNS (4), /* fp_add */ |
| COSTS_N_INSNS (4), /* fp_mult_sf */ |
| COSTS_N_INSNS (5), /* fp_mult_df */ |
| COSTS_N_INSNS (17), /* fp_div_sf */ |
| COSTS_N_INSNS (32), /* fp_div_df */ |
| COSTS_N_INSNS (4), /* int_mult_si */ |
| COSTS_N_INSNS (7), /* int_mult_di */ |
| COSTS_N_INSNS (42), /* int_div_si */ |
| COSTS_N_INSNS (72), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* 24KC */ |
| SOFT_FP_COSTS, |
| COSTS_N_INSNS (5), /* int_mult_si */ |
| COSTS_N_INSNS (5), /* int_mult_di */ |
| COSTS_N_INSNS (41), /* int_div_si */ |
| COSTS_N_INSNS (41), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* 24KF2_1 */ |
| COSTS_N_INSNS (8), /* fp_add */ |
| COSTS_N_INSNS (8), /* fp_mult_sf */ |
| COSTS_N_INSNS (10), /* fp_mult_df */ |
| COSTS_N_INSNS (34), /* fp_div_sf */ |
| COSTS_N_INSNS (64), /* fp_div_df */ |
| COSTS_N_INSNS (5), /* int_mult_si */ |
| COSTS_N_INSNS (5), /* int_mult_di */ |
| COSTS_N_INSNS (41), /* int_div_si */ |
| COSTS_N_INSNS (41), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* 24KF1_1 */ |
| COSTS_N_INSNS (4), /* fp_add */ |
| COSTS_N_INSNS (4), /* fp_mult_sf */ |
| COSTS_N_INSNS (5), /* fp_mult_df */ |
| COSTS_N_INSNS (17), /* fp_div_sf */ |
| COSTS_N_INSNS (32), /* fp_div_df */ |
| COSTS_N_INSNS (5), /* int_mult_si */ |
| COSTS_N_INSNS (5), /* int_mult_di */ |
| COSTS_N_INSNS (41), /* int_div_si */ |
| COSTS_N_INSNS (41), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* 74KC */ |
| SOFT_FP_COSTS, |
| COSTS_N_INSNS (5), /* int_mult_si */ |
| COSTS_N_INSNS (5), /* int_mult_di */ |
| COSTS_N_INSNS (41), /* int_div_si */ |
| COSTS_N_INSNS (41), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* 74KF2_1 */ |
| COSTS_N_INSNS (8), /* fp_add */ |
| COSTS_N_INSNS (8), /* fp_mult_sf */ |
| COSTS_N_INSNS (10), /* fp_mult_df */ |
| COSTS_N_INSNS (34), /* fp_div_sf */ |
| COSTS_N_INSNS (64), /* fp_div_df */ |
| COSTS_N_INSNS (5), /* int_mult_si */ |
| COSTS_N_INSNS (5), /* int_mult_di */ |
| COSTS_N_INSNS (41), /* int_div_si */ |
| COSTS_N_INSNS (41), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* 74KF1_1 */ |
| COSTS_N_INSNS (4), /* fp_add */ |
| COSTS_N_INSNS (4), /* fp_mult_sf */ |
| COSTS_N_INSNS (5), /* fp_mult_df */ |
| COSTS_N_INSNS (17), /* fp_div_sf */ |
| COSTS_N_INSNS (32), /* fp_div_df */ |
| COSTS_N_INSNS (5), /* int_mult_si */ |
| COSTS_N_INSNS (5), /* int_mult_di */ |
| COSTS_N_INSNS (41), /* int_div_si */ |
| COSTS_N_INSNS (41), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* 74KF3_2 */ |
| COSTS_N_INSNS (6), /* fp_add */ |
| COSTS_N_INSNS (6), /* fp_mult_sf */ |
| COSTS_N_INSNS (7), /* fp_mult_df */ |
| COSTS_N_INSNS (25), /* fp_div_sf */ |
| COSTS_N_INSNS (48), /* fp_div_df */ |
| COSTS_N_INSNS (5), /* int_mult_si */ |
| COSTS_N_INSNS (5), /* int_mult_di */ |
| COSTS_N_INSNS (41), /* int_div_si */ |
| COSTS_N_INSNS (41), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* Loongson-2E */ |
| DEFAULT_COSTS |
| }, |
| { /* Loongson-2F */ |
| DEFAULT_COSTS |
| }, |
| { /* Loongson gs464. */ |
| DEFAULT_COSTS |
| }, |
| { /* Loongson gs464e. */ |
| DEFAULT_COSTS |
| }, |
| { /* Loongson gs264e. */ |
| DEFAULT_COSTS |
| }, |
| { /* M4k */ |
| DEFAULT_COSTS |
| }, |
| /* Octeon */ |
| { |
| SOFT_FP_COSTS, |
| COSTS_N_INSNS (5), /* int_mult_si */ |
| COSTS_N_INSNS (5), /* int_mult_di */ |
| COSTS_N_INSNS (72), /* int_div_si */ |
| COSTS_N_INSNS (72), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| /* Octeon II */ |
| { |
| SOFT_FP_COSTS, |
| COSTS_N_INSNS (6), /* int_mult_si */ |
| COSTS_N_INSNS (6), /* int_mult_di */ |
| COSTS_N_INSNS (18), /* int_div_si */ |
| COSTS_N_INSNS (35), /* int_div_di */ |
| 4, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| /* Octeon III */ |
| { |
| COSTS_N_INSNS (6), /* fp_add */ |
| COSTS_N_INSNS (6), /* fp_mult_sf */ |
| COSTS_N_INSNS (7), /* fp_mult_df */ |
| COSTS_N_INSNS (25), /* fp_div_sf */ |
| COSTS_N_INSNS (48), /* fp_div_df */ |
| COSTS_N_INSNS (6), /* int_mult_si */ |
| COSTS_N_INSNS (6), /* int_mult_di */ |
| COSTS_N_INSNS (18), /* int_div_si */ |
| COSTS_N_INSNS (35), /* int_div_di */ |
| 4, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* R3900 */ |
| COSTS_N_INSNS (2), /* fp_add */ |
| COSTS_N_INSNS (4), /* fp_mult_sf */ |
| COSTS_N_INSNS (5), /* fp_mult_df */ |
| COSTS_N_INSNS (12), /* fp_div_sf */ |
| COSTS_N_INSNS (19), /* fp_div_df */ |
| COSTS_N_INSNS (2), /* int_mult_si */ |
| COSTS_N_INSNS (2), /* int_mult_di */ |
| COSTS_N_INSNS (35), /* int_div_si */ |
| COSTS_N_INSNS (35), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* R6000 */ |
| COSTS_N_INSNS (3), /* fp_add */ |
| COSTS_N_INSNS (5), /* fp_mult_sf */ |
| COSTS_N_INSNS (6), /* fp_mult_df */ |
| COSTS_N_INSNS (15), /* fp_div_sf */ |
| COSTS_N_INSNS (16), /* fp_div_df */ |
| COSTS_N_INSNS (17), /* int_mult_si */ |
| COSTS_N_INSNS (17), /* int_mult_di */ |
| COSTS_N_INSNS (38), /* int_div_si */ |
| COSTS_N_INSNS (38), /* int_div_di */ |
| 2, /* branch_cost */ |
| 6 /* memory_latency */ |
| }, |
| { /* R4000 */ |
| COSTS_N_INSNS (6), /* fp_add */ |
| COSTS_N_INSNS (7), /* fp_mult_sf */ |
| COSTS_N_INSNS (8), /* fp_mult_df */ |
| COSTS_N_INSNS (23), /* fp_div_sf */ |
| COSTS_N_INSNS (36), /* fp_div_df */ |
| COSTS_N_INSNS (10), /* int_mult_si */ |
| COSTS_N_INSNS (10), /* int_mult_di */ |
| COSTS_N_INSNS (69), /* int_div_si */ |
| COSTS_N_INSNS (69), /* int_div_di */ |
| 2, /* branch_cost */ |
| 6 /* memory_latency */ |
| }, |
| { /* R4100 */ |
| DEFAULT_COSTS |
| }, |
| { /* R4111 */ |
| DEFAULT_COSTS |
| }, |
| { /* R4120 */ |
| DEFAULT_COSTS |
| }, |
| { /* R4130 */ |
| /* The only costs that appear to be updated here are |
| integer multiplication. */ |
| SOFT_FP_COSTS, |
| COSTS_N_INSNS (4), /* int_mult_si */ |
| COSTS_N_INSNS (6), /* int_mult_di */ |
| COSTS_N_INSNS (69), /* int_div_si */ |
| COSTS_N_INSNS (69), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* R4300 */ |
| DEFAULT_COSTS |
| }, |
| { /* R4600 */ |
| DEFAULT_COSTS |
| }, |
| { /* R4650 */ |
| DEFAULT_COSTS |
| }, |
| { /* R4700 */ |
| DEFAULT_COSTS |
| }, |
| { /* R5000 */ |
| COSTS_N_INSNS (6), /* fp_add */ |
| COSTS_N_INSNS (4), /* fp_mult_sf */ |
| COSTS_N_INSNS (5), /* fp_mult_df */ |
| COSTS_N_INSNS (23), /* fp_div_sf */ |
| COSTS_N_INSNS (36), /* fp_div_df */ |
| COSTS_N_INSNS (5), /* int_mult_si */ |
| COSTS_N_INSNS (5), /* int_mult_di */ |
| COSTS_N_INSNS (36), /* int_div_si */ |
| COSTS_N_INSNS (36), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* R5400 */ |
| COSTS_N_INSNS (6), /* fp_add */ |
| COSTS_N_INSNS (5), /* fp_mult_sf */ |
| COSTS_N_INSNS (6), /* fp_mult_df */ |
| COSTS_N_INSNS (30), /* fp_div_sf */ |
| COSTS_N_INSNS (59), /* fp_div_df */ |
| COSTS_N_INSNS (3), /* int_mult_si */ |
| COSTS_N_INSNS (4), /* int_mult_di */ |
| COSTS_N_INSNS (42), /* int_div_si */ |
| COSTS_N_INSNS (74), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* R5500 */ |
| COSTS_N_INSNS (6), /* fp_add */ |
| COSTS_N_INSNS (5), /* fp_mult_sf */ |
| COSTS_N_INSNS (6), /* fp_mult_df */ |
| COSTS_N_INSNS (30), /* fp_div_sf */ |
| COSTS_N_INSNS (59), /* fp_div_df */ |
| COSTS_N_INSNS (5), /* int_mult_si */ |
| COSTS_N_INSNS (9), /* int_mult_di */ |
| COSTS_N_INSNS (42), /* int_div_si */ |
| COSTS_N_INSNS (74), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* R5900 */ |
| COSTS_N_INSNS (4), /* fp_add */ |
| COSTS_N_INSNS (4), /* fp_mult_sf */ |
| COSTS_N_INSNS (256), /* fp_mult_df */ |
| COSTS_N_INSNS (8), /* fp_div_sf */ |
| COSTS_N_INSNS (256), /* fp_div_df */ |
| COSTS_N_INSNS (4), /* int_mult_si */ |
| COSTS_N_INSNS (256), /* int_mult_di */ |
| COSTS_N_INSNS (37), /* int_div_si */ |
| COSTS_N_INSNS (256), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* R7000 */ |
| /* The only costs that are changed here are |
| integer multiplication. */ |
| COSTS_N_INSNS (6), /* fp_add */ |
| COSTS_N_INSNS (7), /* fp_mult_sf */ |
| COSTS_N_INSNS (8), /* fp_mult_df */ |
| COSTS_N_INSNS (23), /* fp_div_sf */ |
| COSTS_N_INSNS (36), /* fp_div_df */ |
| COSTS_N_INSNS (5), /* int_mult_si */ |
| COSTS_N_INSNS (9), /* int_mult_di */ |
| COSTS_N_INSNS (69), /* int_div_si */ |
| COSTS_N_INSNS (69), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* R8000 */ |
| DEFAULT_COSTS |
| }, |
| { /* R9000 */ |
| /* The only costs that are changed here are |
| integer multiplication. */ |
| COSTS_N_INSNS (6), /* fp_add */ |
| COSTS_N_INSNS (7), /* fp_mult_sf */ |
| COSTS_N_INSNS (8), /* fp_mult_df */ |
| COSTS_N_INSNS (23), /* fp_div_sf */ |
| COSTS_N_INSNS (36), /* fp_div_df */ |
| COSTS_N_INSNS (3), /* int_mult_si */ |
| COSTS_N_INSNS (8), /* int_mult_di */ |
| COSTS_N_INSNS (69), /* int_div_si */ |
| COSTS_N_INSNS (69), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* R1x000 */ |
| COSTS_N_INSNS (2), /* fp_add */ |
| COSTS_N_INSNS (2), /* fp_mult_sf */ |
| COSTS_N_INSNS (2), /* fp_mult_df */ |
| COSTS_N_INSNS (12), /* fp_div_sf */ |
| COSTS_N_INSNS (19), /* fp_div_df */ |
| COSTS_N_INSNS (5), /* int_mult_si */ |
| COSTS_N_INSNS (9), /* int_mult_di */ |
| COSTS_N_INSNS (34), /* int_div_si */ |
| COSTS_N_INSNS (66), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* SB1 */ |
| /* These costs are the same as the SB-1A below. */ |
| COSTS_N_INSNS (4), /* fp_add */ |
| COSTS_N_INSNS (4), /* fp_mult_sf */ |
| COSTS_N_INSNS (4), /* fp_mult_df */ |
| COSTS_N_INSNS (24), /* fp_div_sf */ |
| COSTS_N_INSNS (32), /* fp_div_df */ |
| COSTS_N_INSNS (3), /* int_mult_si */ |
| COSTS_N_INSNS (4), /* int_mult_di */ |
| COSTS_N_INSNS (36), /* int_div_si */ |
| COSTS_N_INSNS (68), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* SB1-A */ |
| /* These costs are the same as the SB-1 above. */ |
| COSTS_N_INSNS (4), /* fp_add */ |
| COSTS_N_INSNS (4), /* fp_mult_sf */ |
| COSTS_N_INSNS (4), /* fp_mult_df */ |
| COSTS_N_INSNS (24), /* fp_div_sf */ |
| COSTS_N_INSNS (32), /* fp_div_df */ |
| COSTS_N_INSNS (3), /* int_mult_si */ |
| COSTS_N_INSNS (4), /* int_mult_di */ |
| COSTS_N_INSNS (36), /* int_div_si */ |
| COSTS_N_INSNS (68), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* SR71000 */ |
| DEFAULT_COSTS |
| }, |
| { /* XLR */ |
| SOFT_FP_COSTS, |
| COSTS_N_INSNS (8), /* int_mult_si */ |
| COSTS_N_INSNS (8), /* int_mult_di */ |
| COSTS_N_INSNS (72), /* int_div_si */ |
| COSTS_N_INSNS (72), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* XLP */ |
| /* These costs are the same as 5KF above. */ |
| COSTS_N_INSNS (4), /* fp_add */ |
| COSTS_N_INSNS (4), /* fp_mult_sf */ |
| COSTS_N_INSNS (5), /* fp_mult_df */ |
| COSTS_N_INSNS (17), /* fp_div_sf */ |
| COSTS_N_INSNS (32), /* fp_div_df */ |
| COSTS_N_INSNS (4), /* int_mult_si */ |
| COSTS_N_INSNS (11), /* int_mult_di */ |
| COSTS_N_INSNS (36), /* int_div_si */ |
| COSTS_N_INSNS (68), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* P5600 */ |
| COSTS_N_INSNS (4), /* fp_add */ |
| COSTS_N_INSNS (5), /* fp_mult_sf */ |
| COSTS_N_INSNS (5), /* fp_mult_df */ |
| COSTS_N_INSNS (17), /* fp_div_sf */ |
| COSTS_N_INSNS (17), /* fp_div_df */ |
| COSTS_N_INSNS (5), /* int_mult_si */ |
| COSTS_N_INSNS (5), /* int_mult_di */ |
| COSTS_N_INSNS (8), /* int_div_si */ |
| COSTS_N_INSNS (8), /* int_div_di */ |
| 2, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* M5100 */ |
| COSTS_N_INSNS (4), /* fp_add */ |
| COSTS_N_INSNS (4), /* fp_mult_sf */ |
| COSTS_N_INSNS (5), /* fp_mult_df */ |
| COSTS_N_INSNS (17), /* fp_div_sf */ |
| COSTS_N_INSNS (32), /* fp_div_df */ |
| COSTS_N_INSNS (5), /* int_mult_si */ |
| COSTS_N_INSNS (5), /* int_mult_di */ |
| COSTS_N_INSNS (34), /* int_div_si */ |
| COSTS_N_INSNS (68), /* int_div_di */ |
| 1, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* I6400 */ |
| COSTS_N_INSNS (4), /* fp_add */ |
| COSTS_N_INSNS (5), /* fp_mult_sf */ |
| COSTS_N_INSNS (5), /* fp_mult_df */ |
| COSTS_N_INSNS (32), /* fp_div_sf */ |
| COSTS_N_INSNS (32), /* fp_div_df */ |
| COSTS_N_INSNS (5), /* int_mult_si */ |
| COSTS_N_INSNS (5), /* int_mult_di */ |
| COSTS_N_INSNS (36), /* int_div_si */ |
| COSTS_N_INSNS (36), /* int_div_di */ |
| 2, /* branch_cost */ |
| 4 /* memory_latency */ |
| }, |
| { /* P6600 */ |
| COSTS_N_INSNS (4), /* fp_add */ |
| COSTS_N_INSNS (5), /* fp_mult_sf */ |
| COSTS_N_INSNS (5), /* fp_mult_df */ |
| COSTS_N_INSNS (17), /* fp_div_sf */ |
| COSTS_N_INSNS (17), /* fp_div_df */ |
| COSTS_N_INSNS (5), /* int_mult_si */ |
| COSTS_N_INSNS (5), /* int_mult_di */ |
| COSTS_N_INSNS (8), /* int_div_si */ |
| COSTS_N_INSNS (8), /* int_div_di */ |
| 2, /* branch_cost */ |
| 4 /* memory_latency */ |
| } |
| }; |
| |
| static rtx mips_find_pic_call_symbol (rtx_insn *, rtx, bool); |
| static int mips_register_move_cost (machine_mode, reg_class_t, |
| reg_class_t); |
| static unsigned int mips_function_arg_boundary (machine_mode, const_tree); |
| static rtx mips_gen_const_int_vector_shuffle (machine_mode, int); |
| |
| /* This hash table keeps track of implicit "mips16" and "nomips16" attributes |
| for -mflip_mips16. It maps decl names onto a boolean mode setting. */ |
| static GTY (()) hash_map<nofree_string_hash, bool> *mflip_mips16_htab; |
| |
| /* True if -mflip-mips16 should next add an attribute for the default MIPS16 |
| mode, false if it should next add an attribute for the opposite mode. */ |
| static GTY(()) bool mips16_flipper; |
| |
| /* DECL is a function that needs a default "mips16" or "nomips16" attribute |
| for -mflip-mips16. Return true if it should use "mips16" and false if |
| it should use "nomips16". */ |
| |
| static bool |
| mflip_mips16_use_mips16_p (tree decl) |
| { |
| const char *name; |
| bool base_is_mips16 = (mips_base_compression_flags & MASK_MIPS16) != 0; |
| |
| /* Use the opposite of the command-line setting for anonymous decls. */ |
| if (!DECL_NAME (decl)) |
| return !base_is_mips16; |
| |
| if (!mflip_mips16_htab) |
| mflip_mips16_htab = hash_map<nofree_string_hash, bool>::create_ggc (37); |
| |
| name = IDENTIFIER_POINTER (DECL_NAME (decl)); |
| |
| bool existed; |
| bool *slot = &mflip_mips16_htab->get_or_insert (name, &existed); |
| if (!existed) |
| { |
| mips16_flipper = !mips16_flipper; |
| *slot = mips16_flipper ? !base_is_mips16 : base_is_mips16; |
| } |
| return *slot; |
| } |
| |
| /* Predicates to test for presence of "near"/"short_call" and "far"/"long_call" |
| attributes on the given TYPE. */ |
| |
| static bool |
| mips_near_type_p (const_tree type) |
| { |
| return (lookup_attribute ("short_call", TYPE_ATTRIBUTES (type)) != NULL |
| || lookup_attribute ("near", TYPE_ATTRIBUTES (type)) != NULL); |
| } |
| |
| static bool |
| mips_far_type_p (const_tree type) |
| { |
| return (lookup_attribute ("long_call", TYPE_ATTRIBUTES (type)) != NULL |
| || lookup_attribute ("far", TYPE_ATTRIBUTES (type)) != NULL); |
| } |
| |
| |
| /* Check if the interrupt attribute is set for a function. */ |
| |
| static bool |
| mips_interrupt_type_p (tree type) |
| { |
| return lookup_attribute ("interrupt", TYPE_ATTRIBUTES (type)) != NULL; |
| } |
| |
| /* Return the mask for the "interrupt" attribute. */ |
| |
| static enum mips_int_mask |
| mips_interrupt_mask (tree type) |
| { |
| tree attr = lookup_attribute ("interrupt", TYPE_ATTRIBUTES (type)); |
| tree args, cst; |
| const char *str; |
| |
| /* For missing attributes or no arguments then return 'eic' as a safe |
| fallback. */ |
| if (attr == NULL) |
| return INT_MASK_EIC; |
| |
| args = TREE_VALUE (attr); |
| |
| if (args == NULL) |
| return INT_MASK_EIC; |
| |
| cst = TREE_VALUE (args); |
| |
| if (strcmp (TREE_STRING_POINTER (cst), "eic") == 0) |
| return INT_MASK_EIC; |
| |
| /* The validation code in mips_handle_interrupt_attr guarantees that the |
| argument is now in the form: |
| vector=(sw0|sw1|hw0|hw1|hw2|hw3|hw4|hw5). */ |
| str = TREE_STRING_POINTER (cst); |
| |
| gcc_assert (strlen (str) == strlen ("vector=sw0")); |
| |
| if (str[7] == 's') |
| return (enum mips_int_mask) (INT_MASK_SW0 + (str[9] - '0')); |
| |
| return (enum mips_int_mask) (INT_MASK_HW0 + (str[9] - '0')); |
| } |
| |
| /* Return the mips_shadow_set if the "use_shadow_register_set" attribute is |
| set for a function. */ |
| |
| static enum mips_shadow_set |
| mips_use_shadow_register_set (tree type) |
| { |
| tree attr = lookup_attribute ("use_shadow_register_set", |
| TYPE_ATTRIBUTES (type)); |
| tree args; |
| |
| /* The validation code in mips_handle_use_shadow_register_set_attr guarantees |
| that if an argument is present then it means: Assume the shadow register |
| set has a valid stack pointer in it. */ |
| if (attr == NULL) |
| return SHADOW_SET_NO; |
| |
| args = TREE_VALUE (attr); |
| |
| if (args == NULL) |
| return SHADOW_SET_YES; |
| |
| return SHADOW_SET_INTSTACK; |
| } |
| |
| /* Check if the attribute to keep interrupts masked is set for a function. */ |
| |
| static bool |
| mips_keep_interrupts_masked_p (tree type) |
| { |
| return lookup_attribute ("keep_interrupts_masked", |
| TYPE_ATTRIBUTES (type)) != NULL; |
| } |
| |
| /* Check if the attribute to use debug exception return is set for |
| a function. */ |
| |
| static bool |
| mips_use_debug_exception_return_p (tree type) |
| { |
| return lookup_attribute ("use_debug_exception_return", |
| TYPE_ATTRIBUTES (type)) != NULL; |
| } |
| |
| /* Return the set of compression modes that are explicitly required |
| by the attributes in ATTRIBUTES. */ |
| |
| static unsigned int |
| mips_get_compress_on_flags (tree attributes) |
| { |
| unsigned int flags = 0; |
| |
| if (lookup_attribute ("mips16", attributes) != NULL) |
| flags |= MASK_MIPS16; |
| |
| if (lookup_attribute ("micromips", attributes) != NULL) |
| flags |= MASK_MICROMIPS; |
| |
| return flags; |
| } |
| |
| /* Return the set of compression modes that are explicitly forbidden |
| by the attributes in ATTRIBUTES. */ |
| |
| static unsigned int |
| mips_get_compress_off_flags (tree attributes) |
| { |
| unsigned int flags = 0; |
| |
| if (lookup_attribute ("nocompression", attributes) != NULL) |
| flags |= MASK_MIPS16 | MASK_MICROMIPS; |
| |
| if (lookup_attribute ("nomips16", attributes) != NULL) |
| flags |= MASK_MIPS16; |
| |
| if (lookup_attribute ("nomicromips", attributes) != NULL) |
| flags |= MASK_MICROMIPS; |
| |
| return flags; |
| } |
| |
| /* Return the compression mode that should be used for function DECL. |
| Return the ambient setting if DECL is null. */ |
| |
| static unsigned int |
| mips_get_compress_mode (tree decl) |
| { |
| unsigned int flags, force_on; |
| |
| flags = mips_base_compression_flags; |
| if (decl) |
| { |
| /* Nested functions must use the same frame pointer as their |
| parent and must therefore use the same ISA mode. */ |
| tree parent = decl_function_context (decl); |
| if (parent) |
| decl = parent; |
| force_on = mips_get_compress_on_flags (DECL_ATTRIBUTES (decl)); |
| if (force_on) |
| return force_on; |
| flags &= ~mips_get_compress_off_flags (DECL_ATTRIBUTES (decl)); |
| } |
| return flags; |
| } |
| |
| /* Return the attribute name associated with MASK_MIPS16 and MASK_MICROMIPS |
| flags FLAGS. */ |
| |
| static const char * |
| mips_get_compress_on_name (unsigned int flags) |
| { |
| if (flags == MASK_MIPS16) |
| return "mips16"; |
| return "micromips"; |
| } |
| |
| /* Return the attribute name that forbids MASK_MIPS16 and MASK_MICROMIPS |
| flags FLAGS. */ |
| |
| static const char * |
| mips_get_compress_off_name (unsigned int flags) |
| { |
| if (flags == MASK_MIPS16) |
| return "nomips16"; |
| if (flags == MASK_MICROMIPS) |
| return "nomicromips"; |
| return "nocompression"; |
| } |
| |
| /* Implement TARGET_COMP_TYPE_ATTRIBUTES. */ |
| |
| static int |
| mips_comp_type_attributes (const_tree type1, const_tree type2) |
| { |
| /* Disallow mixed near/far attributes. */ |
| if (mips_far_type_p (type1) && mips_near_type_p (type2)) |
| return 0; |
| if (mips_near_type_p (type1) && mips_far_type_p (type2)) |
| return 0; |
| return 1; |
| } |
| |
| /* Implement TARGET_INSERT_ATTRIBUTES. */ |
| |
| static void |
| mips_insert_attributes (tree decl, tree *attributes) |
| { |
| const char *name; |
| unsigned int compression_flags, nocompression_flags; |
| |
| /* Check for "mips16" and "nomips16" attributes. */ |
| compression_flags = mips_get_compress_on_flags (*attributes); |
| nocompression_flags = mips_get_compress_off_flags (*attributes); |
| |
| if (TREE_CODE (decl) != FUNCTION_DECL) |
| { |
| if (nocompression_flags) |
| error ("%qs attribute only applies to functions", |
| mips_get_compress_off_name (nocompression_flags)); |
| |
| if (compression_flags) |
| error ("%qs attribute only applies to functions", |
| mips_get_compress_on_name (nocompression_flags)); |
| } |
| else |
| { |
| compression_flags |= mips_get_compress_on_flags (DECL_ATTRIBUTES (decl)); |
| nocompression_flags |= |
| mips_get_compress_off_flags (DECL_ATTRIBUTES (decl)); |
| |
| if (compression_flags && nocompression_flags) |
| error ("%qE cannot have both %qs and %qs attributes", |
| DECL_NAME (decl), mips_get_compress_on_name (compression_flags), |
| mips_get_compress_off_name (nocompression_flags)); |
| |
| if (compression_flags & MASK_MIPS16 |
| && compression_flags & MASK_MICROMIPS) |
| error ("%qE cannot have both %qs and %qs attributes", |
| DECL_NAME (decl), "mips16", "micromips"); |
| |
| if (TARGET_FLIP_MIPS16 |
| && !DECL_ARTIFICIAL (decl) |
| && compression_flags == 0 |
| && nocompression_flags == 0) |
| { |
| /* Implement -mflip-mips16. If DECL has neither a "nomips16" nor a |
| "mips16" attribute, arbitrarily pick one. We must pick the same |
| setting for duplicate declarations of a function. */ |
| name = mflip_mips16_use_mips16_p (decl) ? "mips16" : "nomips16"; |
| *attributes = tree_cons (get_identifier (name), NULL, *attributes); |
| name = "nomicromips"; |
| *attributes = tree_cons (get_identifier (name), NULL, *attributes); |
| } |
| } |
| } |
| |
| /* Implement TARGET_MERGE_DECL_ATTRIBUTES. */ |
| |
| static tree |
| mips_merge_decl_attributes (tree olddecl, tree newdecl) |
| { |
| unsigned int diff; |
| |
| diff = (mips_get_compress_on_flags (DECL_ATTRIBUTES (olddecl)) |
| ^ mips_get_compress_on_flags (DECL_ATTRIBUTES (newdecl))); |
| if (diff) |
| error ("%qE redeclared with conflicting %qs attributes", |
| DECL_NAME (newdecl), mips_get_compress_on_name (diff)); |
| |
| diff = (mips_get_compress_off_flags (DECL_ATTRIBUTES (olddecl)) |
| ^ mips_get_compress_off_flags (DECL_ATTRIBUTES (newdecl))); |
| if (diff) |
| error ("%qE redeclared with conflicting %qs attributes", |
| DECL_NAME (newdecl), mips_get_compress_off_name (diff)); |
| |
| return merge_attributes (DECL_ATTRIBUTES (olddecl), |
| DECL_ATTRIBUTES (newdecl)); |
| } |
| |
| /* Implement TARGET_CAN_INLINE_P. */ |
| |
| static bool |
| mips_can_inline_p (tree caller, tree callee) |
| { |
| if (mips_get_compress_mode (callee) != mips_get_compress_mode (caller)) |
| return false; |
| return default_target_can_inline_p (caller, callee); |
| } |
| |
| /* Handle an "interrupt" attribute with an optional argument. */ |
| |
| static tree |
| mips_handle_interrupt_attr (tree *node ATTRIBUTE_UNUSED, tree name, tree args, |
| int flags ATTRIBUTE_UNUSED, bool *no_add_attrs) |
| { |
| /* Check for an argument. */ |
| if (is_attribute_p ("interrupt", name) && args != NULL) |
| { |
| tree cst; |
| |
| cst = TREE_VALUE (args); |
| if (TREE_CODE (cst) != STRING_CST) |
| { |
| warning (OPT_Wattributes, |
| "%qE attribute requires a string argument", |
| name); |
| *no_add_attrs = true; |
| } |
| else if (strcmp (TREE_STRING_POINTER (cst), "eic") != 0 |
| && !startswith (TREE_STRING_POINTER (cst), "vector=")) |
| { |
| warning (OPT_Wattributes, |
| "argument to %qE attribute is neither eic, nor " |
| "vector=<line>", name); |
| *no_add_attrs = true; |
| } |
| else if (startswith (TREE_STRING_POINTER (cst), "vector=")) |
| { |
| const char *arg = TREE_STRING_POINTER (cst) + 7; |
| |
| /* Acceptable names are: sw0,sw1,hw0,hw1,hw2,hw3,hw4,hw5. */ |
| if (strlen (arg) != 3 |
| || (arg[0] != 's' && arg[0] != 'h') |
| || arg[1] != 'w' |
| || (arg[0] == 's' && arg[2] != '0' && arg[2] != '1') |
| || (arg[0] == 'h' && (arg[2] < '0' || arg[2] > '5'))) |
| { |
| warning (OPT_Wattributes, |
| "interrupt vector to %qE attribute is not " |
| "vector=(sw0|sw1|hw0|hw1|hw2|hw3|hw4|hw5)", |
| name); |
| *no_add_attrs = true; |
| } |
| } |
| |
| return NULL_TREE; |
| } |
| |
| return NULL_TREE; |
| } |
| |
| /* Handle a "use_shadow_register_set" attribute with an optional argument. */ |
| |
| static tree |
| mips_handle_use_shadow_register_set_attr (tree *node ATTRIBUTE_UNUSED, |
| tree name, tree args, |
| int flags ATTRIBUTE_UNUSED, |
| bool *no_add_attrs) |
| { |
| /* Check for an argument. */ |
| if (is_attribute_p ("use_shadow_register_set", name) && args != NULL) |
| { |
| tree cst; |
| |
| cst = TREE_VALUE (args); |
| if (TREE_CODE (cst) != STRING_CST) |
| { |
| warning (OPT_Wattributes, |
| "%qE attribute requires a string argument", |
| name); |
| *no_add_attrs = true; |
| } |
| else if (strcmp (TREE_STRING_POINTER (cst), "intstack") != 0) |
| { |
| warning (OPT_Wattributes, |
| "argument to %qE attribute is not intstack", name); |
| *no_add_attrs = true; |
| } |
| |
| return NULL_TREE; |
| } |
| |
| return NULL_TREE; |
| } |
| |
| /* If X is a PLUS of a CONST_INT, return the two terms in *BASE_PTR |
| and *OFFSET_PTR. Return X in *BASE_PTR and 0 in *OFFSET_PTR otherwise. */ |
| |
| static void |
| mips_split_plus (rtx x, rtx *base_ptr, HOST_WIDE_INT *offset_ptr) |
| { |
| if (GET_CODE (x) == PLUS && CONST_INT_P (XEXP (x, 1))) |
| { |
| *base_ptr = XEXP (x, 0); |
| *offset_ptr = INTVAL (XEXP (x, 1)); |
| } |
| else |
| { |
| *base_ptr = x; |
| *offset_ptr = 0; |
| } |
| } |
| |
| static unsigned int mips_build_integer (struct mips_integer_op *, |
| unsigned HOST_WIDE_INT); |
| |
| /* A 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 we want to give the recursive call 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 |
| { |
| /* Either this is a simple LUI/ORI pair, or clearing the lowest 16 |
| bits gives a value with at least 17 trailing zeros. */ |
| i = mips_build_integer (codes, high); |
| codes[i].code = IOR; |
| codes[i].value = value & 0xffff; |
| } |
| return i + 1; |
| } |
| |
| /* Fill CODES with a sequence of rtl operations to load VALUE. |
| Return the number of operations needed. */ |
| |
| static unsigned int |
| mips_build_integer (struct mips_integer_op *codes, |
| unsigned HOST_WIDE_INT value) |
| { |
| if (SMALL_OPERAND (value) |
| || SMALL_OPERAND_UNSIGNED (value) |
| || LUI_OPERAND (value)) |
| { |
| /* The value can be loaded with a single instruction. */ |
| codes[0].code = UNKNOWN; |
| codes[0].value = value; |
| return 1; |
| } |
| else if ((value & 1) != 0 || LUI_OPERAND (CONST_HIGH_PART (value))) |
| { |
| /* Either the constant is a simple LUI/ORI combination or its |
| lowest bit is set. We don't want to shift in this case. */ |
| return mips_build_lower (codes, value); |
| } |
| else if ((value & 0xffff) == 0) |
| { |
| /* The constant will need at least three actions. The lowest |
| 16 bits are clear, so the final action will be a shift. */ |
| return mips_build_shift (codes, value); |
| } |
| else |
| { |
| /* The final action could be a shift, add or inclusive OR. |
| Rather than use a complex condition to select the best |
| approach, try both mips_build_shift and mips_build_lower |
| and pick the one that gives the shortest sequence. |
| Note that this case is only used once per constant. */ |
| struct mips_integer_op alt_codes[MIPS_MAX_INTEGER_OPS]; |
| unsigned int cost, alt_cost; |
| |
| cost = mips_build_shift (codes, value); |
| alt_cost = mips_build_lower (alt_codes, value); |
| if (alt_cost < cost) |
| { |
| memcpy (codes, alt_codes, alt_cost * sizeof (codes[0])); |
| cost = alt_cost; |
| } |
| return cost; |
| } |
| } |
| |
| /* Implement TARGET_LEGITIMATE_CONSTANT_P. */ |
| |
| static bool |
| mips_legitimate_constant_p (machine_mode mode ATTRIBUTE_UNUSED, rtx x) |
| { |
| return mips_const_insns (x) > 0; |
| } |
| |
| /* Return a SYMBOL_REF for a MIPS16 function called NAME. */ |
| |
| static rtx |
| mips16_stub_function (const char *name) |
| { |
| rtx x; |
| |
| x = gen_rtx_SYMBOL_REF (Pmode, ggc_strdup (name)); |
| SYMBOL_REF_FLAGS (x) |= (SYMBOL_FLAG_EXTERNAL | SYMBOL_FLAG_FUNCTION); |
| return x; |
| } |
| |
| /* Return a legitimate call address for STUB, given that STUB is a MIPS16 |
| support function. */ |
| |
| static rtx |
| mips16_stub_call_address (mips_one_only_stub *stub) |
| { |
| rtx fn = mips16_stub_function (stub->get_name ()); |
| SYMBOL_REF_FLAGS (fn) |= SYMBOL_FLAG_LOCAL; |
| if (!call_insn_operand (fn, VOIDmode)) |
| fn = force_reg (Pmode, fn); |
| return fn; |
| } |
| |
| /* A stub for moving the thread pointer into TLS_GET_TP_REGNUM. */ |
| |
| class mips16_rdhwr_one_only_stub : public mips_one_only_stub |
| { |
| virtual const char *get_name (); |
| virtual void output_body (); |
| }; |
| |
| const char * |
| mips16_rdhwr_one_only_stub::get_name () |
| { |
| return "__mips16_rdhwr"; |
| } |
| |
| void |
| mips16_rdhwr_one_only_stub::output_body () |
| { |
| fprintf (asm_out_file, |
| "\t.set\tpush\n" |
| "\t.set\tmips32r2\n" |
| "\t.set\tnoreorder\n" |
| "\trdhwr\t$3,$29\n" |
| "\t.set\tpop\n" |
| "\tj\t$31\n"); |
| } |
| |
| /* A stub for moving the FCSR into GET_FCSR_REGNUM. */ |
| class mips16_get_fcsr_one_only_stub : public mips_one_only_stub |
| { |
| virtual const char *get_name (); |
| virtual void output_body (); |
| }; |
| |
| const char * |
| mips16_get_fcsr_one_only_stub::get_name () |
| { |
| return "__mips16_get_fcsr"; |
| } |
| |
| void |
| mips16_get_fcsr_one_only_stub::output_body () |
| { |
| fprintf (asm_out_file, |
| "\tcfc1\t%s,$31\n" |
| "\tj\t$31\n", reg_names[GET_FCSR_REGNUM]); |
| } |
| |
| /* A stub for moving SET_FCSR_REGNUM into the FCSR. */ |
| class mips16_set_fcsr_one_only_stub : public mips_one_only_stub |
| { |
| virtual const char *get_name (); |
| virtual void output_body (); |
| }; |
| |
| const char * |
| mips16_set_fcsr_one_only_stub::get_name () |
| { |
| return "__mips16_set_fcsr"; |
| } |
| |
| void |
| mips16_set_fcsr_one_only_stub::output_body () |
| { |
| fprintf (asm_out_file, |
| "\tctc1\t%s,$31\n" |
| "\tj\t$31\n", reg_names[SET_FCSR_REGNUM]); |
| } |
| |
| /* Return true if symbols of type TYPE require a GOT access. */ |
| |
| static bool |
| mips_got_symbol_type_p (enum mips_symbol_type type) |
| { |
| switch (type) |
| { |
| case SYMBOL_GOT_PAGE_OFST: |
| case SYMBOL_GOT_DISP: |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| /* Return true if X is a thread-local symbol. */ |
| |
| static bool |
| mips_tls_symbol_p (rtx x) |
| { |
| return GET_CODE (x) == SYMBOL_REF && SYMBOL_REF_TLS_MODEL (x) != 0; |
| } |
| |
| /* Return true if SYMBOL_REF X is associated with a global symbol |
| (in the STB_GLOBAL sense). */ |
| |
| static bool |
| mips_global_symbol_p (const_rtx x) |
| { |
| const_tree decl = SYMBOL_REF_DECL (x); |
| |
| if (!decl) |
| return !SYMBOL_REF_LOCAL_P (x) || SYMBOL_REF_EXTERNAL_P (x); |
| |
| /* Weakref symbols are not TREE_PUBLIC, but their targets are global |
| or weak symbols. Relocations in the object file will be against |
| the target symbol, so it's that symbol's binding that matters here. */ |
| return DECL_P (decl) && (TREE_PUBLIC (decl) || DECL_WEAK (decl)); |
| } |
| |
| /* Return true if function X is a libgcc MIPS16 stub function. */ |
| |
| static bool |
| mips16_stub_function_p (const_rtx x) |
| { |
| return (GET_CODE (x) == SYMBOL_REF |
| && startswith (XSTR (x, 0), "__mips16_")); |
| } |
| |
| /* Return true if function X is a locally-defined and locally-binding |
| MIPS16 function. */ |
| |
| static bool |
| mips16_local_function_p (const_rtx x) |
| { |
| return (GET_CODE (x) == SYMBOL_REF |
| && SYMBOL_REF_LOCAL_P (x) |
| && !SYMBOL_REF_EXTERNAL_P (x) |
| && (mips_get_compress_mode (SYMBOL_REF_DECL (x)) & MASK_MIPS16)); |
| } |
| |
| /* Return true if SYMBOL_REF X binds locally. */ |
| |
| static bool |
| mips_symbol_binds_local_p (const_rtx x) |
| { |
| return (SYMBOL_REF_DECL (x) |
| ? targetm.binds_local_p (SYMBOL_REF_DECL (x)) |
| : SYMBOL_REF_LOCAL_P (x)); |
| } |
| |
| /* Return true if OP is a constant vector with the number of units in MODE, |
| and each unit has the same bit set. */ |
| |
| bool |
| mips_const_vector_bitimm_set_p (rtx op, machine_mode mode) |
| { |
| if (GET_CODE (op) == CONST_VECTOR && op != CONST0_RTX (mode)) |
| { |
| unsigned HOST_WIDE_INT val = UINTVAL (CONST_VECTOR_ELT (op, 0)); |
| int vlog2 = exact_log2 (val & GET_MODE_MASK (GET_MODE_INNER (mode))); |
| |
| if (vlog2 != -1) |
| { |
| gcc_assert (GET_MODE_CLASS (mode) == MODE_VECTOR_INT); |
| gcc_assert (vlog2 >= 0 && vlog2 <= GET_MODE_UNIT_BITSIZE (mode) - 1); |
| return mips_const_vector_same_val_p (op, mode); |
| } |
| } |
| |
| return false; |
| } |
| |
| /* Return true if OP is a constant vector with the number of units in MODE, |
| and each unit has the same bit clear. */ |
| |
| bool |
| mips_const_vector_bitimm_clr_p (rtx op, machine_mode mode) |
| { |
| if (GET_CODE (op) == CONST_VECTOR && op != CONSTM1_RTX (mode)) |
| { |
| unsigned HOST_WIDE_INT val = ~UINTVAL (CONST_VECTOR_ELT (op, 0)); |
| int vlog2 = exact_log2 (val & GET_MODE_MASK (GET_MODE_INNER (mode))); |
| |
| if (vlog2 != -1) |
| { |
| gcc_assert (GET_MODE_CLASS (mode) == MODE_VECTOR_INT); |
| gcc_assert (vlog2 >= 0 && vlog2 <= GET_MODE_UNIT_BITSIZE (mode) - 1); |
| return mips_const_vector_same_val_p (op, mode); |
| } |
| } |
| |
| return false; |
| } |
| |
| /* Return true if OP is a constant vector with the number of units in MODE, |
| and each unit has the same value. */ |
| |
| bool |
| mips_const_vector_same_val_p (rtx op, machine_mode mode) |
| { |
| int i, nunits = GET_MODE_NUNITS (mode); |
| rtx first; |
| |
| if (GET_CODE (op) != CONST_VECTOR || GET_MODE (op) != mode) |
| return false; |
| |
| first = CONST_VECTOR_ELT (op, 0); |
| for (i = 1; i < nunits; i++) |
| if (!rtx_equal_p (first, CONST_VECTOR_ELT (op, i))) |
| return false; |
| |
| return true; |
| } |
| |
| /* Return true if OP is a constant vector with the number of units in MODE, |
| and each unit has the same value as well as replicated bytes in the value. |
| */ |
| |
| bool |
| mips_const_vector_same_bytes_p (rtx op, machine_mode mode) |
| { |
| int i, bytes; |
| HOST_WIDE_INT val, first_byte; |
| rtx first; |
| |
| if (!mips_const_vector_same_val_p (op, mode)) |
| return false; |
| |
| first = CONST_VECTOR_ELT (op, 0); |
| bytes = GET_MODE_UNIT_SIZE (mode); |
| val = INTVAL (first); |
| first_byte = val & 0xff; |
| for (i = 1; i < bytes; i++) |
| { |
| val >>= 8; |
| if ((val & 0xff) != first_byte) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* Return true if OP is a constant vector with the number of units in MODE, |
| and each unit has the same integer value in the range [LOW, HIGH]. */ |
| |
| bool |
| mips_const_vector_same_int_p (rtx op, machine_mode mode, HOST_WIDE_INT low, |
| HOST_WIDE_INT high) |
| { |
| HOST_WIDE_INT value; |
| rtx elem0; |
| |
| if (!mips_const_vector_same_val_p (op, mode)) |
| return false; |
| |
| elem0 = CONST_VECTOR_ELT (op, 0); |
| if (!CONST_INT_P (elem0)) |
| return false; |
| |
| value = INTVAL (elem0); |
| return (value >= low && value <= high); |
| } |
| |
| /* Return true if OP is a constant vector with repeated 4-element sets |
| in mode MODE. */ |
| |
| bool |
| mips_const_vector_shuffle_set_p (rtx op, machine_mode mode) |
| { |
| int nunits = GET_MODE_NUNITS (mode); |
| int nsets = nunits / 4; |
| int set = 0; |
| int i, j; |
| |
| /* Check if we have the same 4-element sets. */ |
| for (j = 0; j < nsets; j++, set = 4 * j) |
| for (i = 0; i < 4; i++) |
| if ((INTVAL (XVECEXP (op, 0, i)) |
| != (INTVAL (XVECEXP (op, 0, set + i)) - set)) |
| || !IN_RANGE (INTVAL (XVECEXP (op, 0, set + i)), 0, set + 3)) |
| return false; |
| return true; |
| } |
| |
| /* Return true if rtx constants of mode MODE should be put into a small |
| data section. */ |
| |
| static bool |
| mips_rtx_constant_in_small_data_p (machine_mode mode) |
| { |
| return (!TARGET_EMBEDDED_DATA |
| && TARGET_LOCAL_SDATA |
| && GET_MODE_SIZE (mode) <= mips_small_data_threshold); |
| } |
| |
| /* Return true if X should not be moved directly into register $25. |
| We need this because many versions of GAS will treat "la $25,foo" as |
| part of a call sequence and so allow a global "foo" to be lazily bound. */ |
| |
| bool |
| mips_dangerous_for_la25_p (rtx x) |
| { |
| return (!TARGET_EXPLICIT_RELOCS |
| && TARGET_USE_GOT |
| && GET_CODE (x) == SYMBOL_REF |
| && mips_global_symbol_p (x)); |
| } |
| |
| /* Return true if calls to X might need $25 to be valid on entry. */ |
| |
| bool |
| mips_use_pic_fn_addr_reg_p (const_rtx x) |
| { |
| if (!TARGET_USE_PIC_FN_ADDR_REG) |
| return false; |
| |
| /* MIPS16 stub functions are guaranteed not to use $25. */ |
| if (mips16_stub_function_p (x)) |
| return false; |
| |
| if (GET_CODE (x) == SYMBOL_REF) |
| { |
| /* If PLTs and copy relocations are available, the static linker |
| will make sure that $25 is valid on entry to the target function. */ |
| if (TARGET_ABICALLS_PIC0) |
| return false; |
| |
| /* Locally-defined functions use absolute accesses to set up |
| the global pointer. */ |
| if (TARGET_ABSOLUTE_ABICALLS |
| && mips_symbol_binds_local_p (x) |
| && !SYMBOL_REF_EXTERNAL_P (x)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* Return the method that should be used to access SYMBOL_REF or |
| LABEL_REF X in context CONTEXT. */ |
| |
| static enum mips_symbol_type |
| mips_classify_symbol (const_rtx x, enum mips_symbol_context context) |
| { |
| if (TARGET_RTP_PIC) |
| return SYMBOL_GOT_DISP; |
| |
| if (GET_CODE (x) == LABEL_REF) |
| { |
| /* Only return SYMBOL_PC_RELATIVE if we are generating MIPS16 |
| code and if we know that the label is in the current function's |
| text section. LABEL_REFs are used for jump tables as well as |
| text labels, so we must check whether jump tables live in the |
| text section. */ |
| if (TARGET_MIPS16_SHORT_JUMP_TABLES |
| && !LABEL_REF_NONLOCAL_P (x)) |
| return SYMBOL_PC_RELATIVE; |
| |
| if (TARGET_ABICALLS && !TARGET_ABSOLUTE_ABICALLS) |
| return SYMBOL_GOT_PAGE_OFST; |
| |
| return SYMBOL_ABSOLUTE; |
| } |
| |
| gcc_assert (GET_CODE (x) == SYMBOL_REF); |
| |
| if (SYMBOL_REF_TLS_MODEL (x)) |
| return SYMBOL_TLS; |
| |
| if (CONSTANT_POOL_ADDRESS_P (x)) |
| { |
| if (TARGET_MIPS16_TEXT_LOADS) |
| return SYMBOL_PC_RELATIVE; |
| |
| if (TARGET_MIPS16_PCREL_LOADS && context == SYMBOL_CONTEXT_MEM) |
| return SYMBOL_PC_RELATIVE; |
| |
| if (mips_rtx_constant_in_small_data_p (get_pool_mode (x))) |
| return SYMBOL_GP_RELATIVE; |
| } |
| |
| /* Do not use small-data accesses for weak symbols; they may end up |
| being zero. */ |
| if (TARGET_GPOPT && SYMBOL_REF_SMALL_P (x) && !SYMBOL_REF_WEAK (x)) |
| return SYMBOL_GP_RELATIVE; |
| |
| /* Don't use GOT accesses for locally-binding symbols when -mno-shared |
| is in effect. */ |
| if (TARGET_ABICALLS_PIC2 |
| && !(TARGET_ABSOLUTE_ABICALLS && mips_symbol_binds_local_p (x))) |
| { |
| /* 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 depends on the symbol's binding rather |
| than its visibility. |
| |
| 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 (mips_global_symbol_p (x)) |
| return SYMBOL_GOT_DISP; |
| |
| return SYMBOL_GOT_PAGE_OFST; |
| } |
| |
| return SYMBOL_ABSOLUTE; |
| } |
| |
| /* Classify the base of symbolic expression X, given that X appears in |
| context CONTEXT. */ |
| |
| static enum mips_symbol_type |
| mips_classify_symbolic_expression (rtx x, enum mips_symbol_context context) |
| { |
| rtx offset; |
| |
| split_const (x, &x, &offset); |
| if (UNSPEC_ADDRESS_P (x)) |
| return UNSPEC_ADDRESS_TYPE (x); |
| |
| return mips_classify_symbol (x, context); |
| } |
| |
| /* Return true if OFFSET is within the range [0, ALIGN), where ALIGN |
| is the alignment in bytes of SYMBOL_REF X. */ |
| |
| static bool |
| mips_offset_within_alignment_p (rtx x, HOST_WIDE_INT offset) |
| { |
| HOST_WIDE_INT align; |
| |
| align = SYMBOL_REF_DECL (x) ? DECL_ALIGN_UNIT (SYMBOL_REF_DECL (x)) : 1; |
| return IN_RANGE (offset, 0, align - 1); |
| } |
| |
| /* Return true if X is a symbolic constant that can be used in context |
| CONTEXT. If it is, store the type of the symbol in *SYMBOL_TYPE. */ |
| |
| bool |
| mips_symbolic_constant_p (rtx x, enum mips_symbol_context context, |
| enum mips_symbol_type *symbol_type) |
| { |
| rtx offset; |
| |
| split_const (x, &x, &offset); |
| if (UNSPEC_ADDRESS_P (x)) |
| { |
| *symbol_type = UNSPEC_ADDRESS_TYPE (x); |
| x = UNSPEC_ADDRESS (x); |
| } |
| else if (GET_CODE (x) == SYMBOL_REF || GET_CODE (x) == LABEL_REF) |
| { |
| *symbol_type = mips_classify_symbol (x, context); |
| if (*symbol_type == SYMBOL_TLS) |
| return false; |
| } |
| else |
| return false; |
| |
| if (offset == const0_rtx) |
| return true; |
| |
| /* Check whether a nonzero offset is valid for the underlying |
| relocations. */ |
| switch (*symbol_type) |
| { |
| case SYMBOL_ABSOLUTE: |
| case SYMBOL_64_HIGH: |
| case SYMBOL_64_MID: |
| case SYMBOL_64_LOW: |
| /* If the target has 64-bit pointers and the object file only |
| supports 32-bit symbols, the values of those symbols will be |
| sign-extended. In this case we can't allow an arbitrary offset |
| in case the 32-bit value X + OFFSET has a different sign from X. */ |
| if (Pmode == DImode && !ABI_HAS_64BIT_SYMBOLS) |
| return offset_within_block_p (x, INTVAL (offset)); |
| |
| /* In other cases the relocations can handle any offset. */ |
| return true; |
| |
| case SYMBOL_PC_RELATIVE: |
| /* Allow constant pool references to be converted to LABEL+CONSTANT. |
| In this case, we no longer have access to the underlying constant, |
| but the original symbol-based access was known to be valid. */ |
| if (GET_CODE (x) == LABEL_REF) |
| return true; |
| |
| /* Fall through. */ |
| |
| case SYMBOL_GP_RELATIVE: |
| /* Make sure that the offset refers to something within the |
| same object block. This should guarantee that the final |
| PC- or GP-relative offset is within the 16-bit limit. */ |
| return offset_within_block_p (x, INTVAL (offset)); |
| |
| case SYMBOL_GOT_PAGE_OFST: |
| case SYMBOL_GOTOFF_PAGE: |
| /* If the symbol is global, the GOT entry will contain the symbol's |
| address, and we will apply a 16-bit offset after loading it. |
| If the symbol is local, the linker should provide enough local |
| GOT entries for a 16-bit offset, but larger offsets may lead |
| to GOT overflow. */ |
| return SMALL_INT (offset); |
| |
| case SYMBOL_TPREL: |
| case SYMBOL_DTPREL: |
| /* There is no carry between the HI and LO REL relocations, so the |
| offset is only valid if we know it won't lead to such a carry. */ |
| return mips_offset_within_alignment_p (x, INTVAL (offset)); |
| |
| case SYMBOL_GOT_DISP: |
| case SYMBOL_GOTOFF_DISP: |
| case SYMBOL_GOTOFF_CALL: |
| case SYMBOL_GOTOFF_LOADGP: |
| case SYMBOL_TLSGD: |
| case SYMBOL_TLSLDM: |
| case SYMBOL_GOTTPREL: |
| case SYMBOL_TLS: |
| case SYMBOL_HALF: |
| return false; |
| } |
| gcc_unreachable (); |
| } |
| |
| /* Like mips_symbol_insns, but treat extended MIPS16 instructions as a |
| single instruction. We rely on the fact that, in the worst case, |
| all instructions involved in a MIPS16 address calculation are usually |
| extended ones. */ |
| |
| static int |
| mips_symbol_insns_1 (enum mips_symbol_type type, machine_mode mode) |
| { |
| if (mips_use_pcrel_pool_p[(int) type]) |
| { |
| if (mode == MAX_MACHINE_MODE) |
| /* LEAs will be converted into constant-pool references by |
| mips_reorg. */ |
| type = SYMBOL_PC_RELATIVE; |
| else |
| /* The constant must be loaded and then dereferenced. */ |
| return 0; |
| } |
| |
| switch (type) |
| { |
| case SYMBOL_ABSOLUTE: |
| /* 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 for normal mode and |
| a preparatory LI and SLL for MIPS16. */ |
| return ABI_HAS_64BIT_SYMBOLS ? 6 : TARGET_MIPS16 ? 3 : 2; |
| |
| case SYMBOL_GP_RELATIVE: |
| /* Treat GP-relative accesses as taking a single instruction on |
| MIPS16 too; the copy of $gp can often be shared. */ |
| return 1; |
| |
| case SYMBOL_PC_RELATIVE: |
| /* PC-relative constants can be only be used with ADDIUPC, |
| DADDIUPC, LWPC and LDPC. */ |
| if (mode == MAX_MACHINE_MODE |
| || GET_MODE_SIZE (mode) == 4 |
| || GET_MODE_SIZE (mode) == 8) |
| return 1; |
| |
| /* The constant must be loaded using ADDIUPC or DADDIUPC first. */ |
| return 0; |
| |
| case SYMBOL_GOT_DISP: |
| /* The constant will have to be loaded from the GOT before it |
| is used in an address. */ |
| if (mode != MAX_MACHINE_MODE) |
| return 0; |
| |
| /* Fall through. */ |
| |
| case SYMBOL_GOT_PAGE_OFST: |
| /* Unless -funit-at-a-time is in effect, we can't be sure whether the |
| local/global classification is accurate. 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_DISP: |
| case SYMBOL_GOTOFF_CALL: |
| case SYMBOL_GOTOFF_LOADGP: |
| case SYMBOL_64_HIGH: |
| case SYMBOL_64_MID: |
| case SYMBOL_64_LOW: |
| case SYMBOL_TLSGD: |
| case SYMBOL_TLSLDM: |
| case SYMBOL_DTPREL: |
| case SYMBOL_GOTTPREL: |
| case SYMBOL_TPREL: |
| case SYMBOL_HALF: |
| /* A 16-bit constant formed by a single relocation, or a 32-bit |
| constant formed from a high 16-bit relocation and a low 16-bit |
| relocation. Use mips_split_p to determine which. 32-bit |
| constants need an "lui; addiu" sequence for normal mode and |
| an "li; sll; addiu" sequence for MIPS16 mode. */ |
| return !mips_split_p[type] ? 1 : TARGET_MIPS16 ? 3 : 2; |
| |
| case SYMBOL_TLS: |
| /* We don't treat a bare TLS symbol as a constant. */ |
| return 0; |
| } |
| gcc_unreachable (); |
| } |
| |
| /* If MODE is MAX_MACHINE_MODE, return the number of instructions needed |
| to load symbols of type TYPE into a register. Return 0 if the given |
| type of symbol cannot be used as an immediate operand. |
| |
| Otherwise, return the number of instructions needed to load or store |
| values of mode MODE to or from addresses of type TYPE. Return 0 if |
| the given type of symbol is not valid in addresses. |
| |
| In both cases, instruction counts are based off BASE_INSN_LENGTH. */ |
| |
| static int |
| mips_symbol_insns (enum mips_symbol_type type, machine_mode mode) |
| { |
| /* MSA LD.* and ST.* cannot support loading symbols via an immediate |
| operand. */ |
| if (mode != MAX_MACHINE_MODE && MSA_SUPPORTED_MODE_P (mode)) |
| return 0; |
| |
| return mips_symbol_insns_1 (type, mode) * (TARGET_MIPS16 ? 2 : 1); |
| } |
| |
| /* Implement TARGET_CANNOT_FORCE_CONST_MEM. */ |
| |
| static bool |
| mips_cannot_force_const_mem (machine_mode mode, rtx x) |
| { |
| enum mips_symbol_type type; |
| rtx base, offset; |
| |
| /* There is no assembler syntax for expressing an address-sized |
| high part. */ |
| if (GET_CODE (x) == HIGH) |
| return true; |
| |
| /* As an optimization, reject constants that mips_legitimize_move |
| can expand inline. |
| |
| Suppose we have a multi-instruction sequence that loads constant C |
| into register R. If R does not get allocated a hard register, and |
| R is used in an operand that allows both registers and memory |
| references, reload will consider forcing C into memory and using |
| one of the instruction's memory alternatives. Returning false |
| here will force it to use an input reload instead. */ |
| if ((CONST_INT_P (x) || GET_CODE (x) == CONST_VECTOR) |
| && mips_legitimate_constant_p (mode, x)) |
| return true; |
| |
| split_const (x, &base, &offset); |
| if (mips_symbolic_constant_p (base, SYMBOL_CONTEXT_LEA, &type)) |
| { |
| /* See whether we explicitly want these symbols in the pool. */ |
| if (mips_use_pcrel_pool_p[(int) type]) |
| return false; |
| |
| /* The same optimization as for CONST_INT. */ |
| if (SMALL_INT (offset) && mips_symbol_insns (type, MAX_MACHINE_MODE) > 0) |
| return true; |
| |
| /* If MIPS16 constant pools live in the text section, they should |
| not refer to anything that might need run-time relocation. */ |
| if (TARGET_MIPS16_PCREL_LOADS && mips_got_symbol_type_p (type)) |
| return true; |
| } |
| |
| /* TLS symbols must be computed by mips_legitimize_move. */ |
| if (tls_referenced_p (x)) |
| return true; |
| |
| return false; |
| } |
| |
| /* Implement TARGET_USE_BLOCKS_FOR_CONSTANT_P. We can't use blocks for |
| constants when we're using a per-function constant pool. */ |
| |
| static bool |
| mips_use_blocks_for_constant_p (machine_mode mode ATTRIBUTE_UNUSED, |
| const_rtx x ATTRIBUTE_UNUSED) |
| { |
| return !TARGET_MIPS16_PCREL_LOADS; |
| } |
| |
| /* Return true if register REGNO is a valid base register for mode MODE. |
| STRICT_P is true if REG_OK_STRICT is in effect. */ |
| |
| int |
| mips_regno_mode_ok_for_base_p (int regno, machine_mode mode, |
| bool strict_p) |
| { |
| if (!HARD_REGISTER_NUM_P (regno)) |
| { |
| if (!strict_p) |
| 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. */ |
| if (TARGET_MIPS16 && regno == STACK_POINTER_REGNUM) |
| return 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 mode MODE. |
| STRICT_P is true if REG_OK_STRICT is in effect. */ |
| |
| static bool |
| mips_valid_base_register_p (rtx x, machine_mode mode, bool strict_p) |
| { |
| if (!strict_p && GET_CODE (x) == SUBREG) |
| x = SUBREG_REG (x); |
| |
| return (REG_P (x) |
| && mips_regno_mode_ok_for_base_p (REGNO (x), mode, strict_p)); |
| } |
| |
| /* Return true if, for every base register BASE_REG, (plus BASE_REG X) |
| can address a value of mode MODE. */ |
| |
| static bool |
| mips_valid_offset_p (rtx x, machine_mode mode) |
| { |
| /* Check that X is a signed 16-bit number. */ |
| if (!const_arith_operand (x, Pmode)) |
| return false; |
| |
| /* We may need to split multiword moves, so make sure that every word |
| is accessible. */ |
| if (GET_MODE_SIZE (mode) > UNITS_PER_WORD |
| && !SMALL_OPERAND (INTVAL (x) + GET_MODE_SIZE (mode) - UNITS_PER_WORD)) |
| return false; |
| |
| /* MSA LD.* and ST.* supports 10-bit signed offsets. */ |
| if (MSA_SUPPORTED_MODE_P (mode) |
| && !mips_signed_immediate_p (INTVAL (x), 10, |
| mips_ldst_scaled_shift (mode))) |
| return false; |
| |
| return true; |
| } |
| |
| /* Return true if a LO_SUM can address a value of mode MODE when the |
| LO_SUM symbol has type SYMBOL_TYPE. */ |
| |
| static bool |
| mips_valid_lo_sum_p (enum mips_symbol_type symbol_type, machine_mode mode) |
| { |
| /* Check that symbols of type SYMBOL_TYPE can be used to access values |
| of mode MODE. */ |
| if (mips_symbol_insns (symbol_type, mode) == 0) |
| return false; |
| |
| /* Check that there is a known low-part relocation. */ |
| if (mips_lo_relocs[symbol_type] == NULL) |
| return false; |
| |
| /* We may need to split multiword moves, so make sure that each word |
| can be accessed without inducing a carry. This is mainly needed |
| for o64, which has historically only guaranteed 64-bit alignment |
| for 128-bit types. */ |
| if (GET_MODE_SIZE (mode) > UNITS_PER_WORD |
| && GET_MODE_BITSIZE (mode) > GET_MODE_ALIGNMENT (mode)) |
| return false; |
| |
| /* MSA LD.* and ST.* cannot support loading symbols via %lo($base). */ |
| if (MSA_SUPPORTED_MODE_P (mode)) |
| return false; |
| |
| return true; |
| } |
| |
| /* Return true if X is a valid address for machine mode MODE. If it is, |
| fill in INFO appropriately. STRICT_P is true if REG_OK_STRICT is in |
| effect. */ |
| |
| static bool |
| mips_classify_address (struct mips_address_info *info, rtx x, |
| machine_mode mode, bool strict_p) |
| { |
| 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_p); |
| |
| 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_p) |
| && mips_valid_offset_p (info->offset, mode)); |
| |
| case LO_SUM: |
| info->type = ADDRESS_LO_SUM; |
| info->reg = XEXP (x, 0); |
| info->offset = XEXP (x, 1); |
| /* We have to trust the creator of the LO_SUM to do something vaguely |
| sane. Target-independent code that creates a LO_SUM should also |
| create and verify the matching HIGH. Target-independent code that |
| adds an offset to a LO_SUM must prove that the offset will not |
| induce a carry. Failure to do either of these things would be |
| a bug, and we are not required to check for it here. The MIPS |
| backend itself should only create LO_SUMs for valid symbolic |
| constants, with the high part being either a HIGH or a copy |
| of _gp. */ |
| info->symbol_type |
| = mips_classify_symbolic_expression (info->offset, SYMBOL_CONTEXT_MEM); |
| return (mips_valid_base_register_p (info->reg, mode, strict_p) |
| && mips_valid_lo_sum_p (info->symbol_type, mode)); |
| |
| 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, SYMBOL_CONTEXT_MEM, |
| &info->symbol_type) |
| && mips_symbol_insns (info->symbol_type, mode) > 0 |
| && !mips_split_p[info->symbol_type]); |
| |
| default: |
| return false; |
| } |
| } |
| |
| /* Implement TARGET_LEGITIMATE_ADDRESS_P. */ |
| |
| static bool |
| mips_legitimate_address_p (machine_mode mode, rtx x, bool strict_p) |
| { |
| struct mips_address_info addr; |
| |
| return mips_classify_address (&addr, x, mode, strict_p); |
| } |
| |
| /* Return true if X is a legitimate $sp-based address for mode MODE. */ |
| |
| bool |
| mips_stack_address_p (rtx x, machine_mode mode) |
| { |
| struct mips_address_info addr; |
| |
| return (mips_classify_address (&addr, x, mode, false) |
| && addr.type == ADDRESS_REG |
| && addr.reg == stack_pointer_rtx); |
| } |
| |
| /* Return true if ADDR matches the pattern for the LWXS load scaled indexed |
| address instruction. Note that such addresses are not considered |
| legitimate in the TARGET_LEGITIMATE_ADDRESS_P sense, because their use |
| is so restricted. */ |
| |
| static bool |
| mips_lwxs_address_p (rtx addr) |
| { |
| if (ISA_HAS_LWXS |
| && GET_CODE (addr) == PLUS |
| && REG_P (XEXP (addr, 1))) |
| { |
| rtx offset = XEXP (addr, 0); |
| if (GET_CODE (offset) == MULT |
| && REG_P (XEXP (offset, 0)) |
| && CONST_INT_P (XEXP (offset, 1)) |
| && INTVAL (XEXP (offset, 1)) == 4) |
| return true; |
| } |
| return false; |
| } |
| |
| /* Return true if ADDR matches the pattern for the L{B,H,W,D}{,U}X load |
| indexed address instruction. Note that such addresses are |
| not considered legitimate in the TARGET_LEGITIMATE_ADDRESS_P |
| sense, because their use is so restricted. */ |
| |
| static bool |
| mips_lx_address_p (rtx addr, machine_mode mode) |
| { |
| if (GET_CODE (addr) != PLUS |
| || !REG_P (XEXP (addr, 0)) |
| || !REG_P (XEXP (addr, 1))) |
| return false; |
| if (ISA_HAS_LBX && mode == QImode) |
| return true; |
| if (ISA_HAS_LHX && mode == HImode) |
| return true; |
| if (ISA_HAS_LWX && mode == SImode) |
| return true; |
| if (ISA_HAS_LDX && mode == DImode) |
| return true; |
| if (MSA_SUPPORTED_MODE_P (mode)) |
| return true; |
| return false; |
| } |
| |
| /* Return true if a value at OFFSET bytes from base register 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 LH and SH, twice |
| for LW and SW, and so on. An exception is LWSP and SWSP, which have |
| an 8-bit immediate field that's shifted left twice. */ |
| |
| static bool |
| mips16_unextended_reference_p (machine_mode mode, rtx base, |
| unsigned HOST_WIDE_INT offset) |
| { |
| if (mode != BLKmode && offset % GET_MODE_SIZE (mode) == 0) |
| { |
| if (GET_MODE_SIZE (mode) == 4 && base == stack_pointer_rtx) |
| return offset < 256U * GET_MODE_SIZE (mode); |
| return offset < 32U * GET_MODE_SIZE (mode); |
| } |
| return false; |
| } |
| |
| /* Return the number of instructions needed to load or store a value |
| of mode MODE at address X, assuming that BASE_INSN_LENGTH is the |
| length of one instruction. Return 0 if X isn't valid for MODE. |
| Assume that multiword moves may need to be split into word moves |
| if MIGHT_SPLIT_P, otherwise assume that a single load or store is |
| enough. */ |
| |
| int |
| mips_address_insns (rtx x, machine_mode mode, bool might_split_p) |
| { |
| struct mips_address_info addr; |
| int factor; |
| bool msa_p = (!might_split_p && MSA_SUPPORTED_MODE_P (mode)); |
| |
| /* BLKmode is used for single unaligned loads and stores and should |
| not count as a multiword mode. (GET_MODE_SIZE (BLKmode) is pretty |
| meaningless, so we have to single it out as a special case one way |
| or the other.) */ |
| if (mode != BLKmode && might_split_p) |
| factor = (GET_MODE_SIZE (mode) + UNITS_PER_WORD - 1) / UNITS_PER_WORD; |
| else |
| factor = 1; |
| |
| if (mips_classify_address (&addr, x, mode, false)) |
| switch (addr.type) |
| { |
| case ADDRESS_REG: |
| if (msa_p) |
| { |
| /* MSA LD.* and ST.* supports 10-bit signed offsets. */ |
| if (mips_signed_immediate_p (INTVAL (addr.offset), 10, |
| mips_ldst_scaled_shift (mode))) |
| return 1; |
| else |
| return 0; |
| } |
| if (TARGET_MIPS16 |
| && !mips16_unextended_reference_p (mode, addr.reg, |
| UINTVAL (addr.offset))) |
| return factor * 2; |
| return factor; |
| |
| case ADDRESS_LO_SUM: |
| return msa_p ? 0 : TARGET_MIPS16 ? factor * 2 : factor; |
| |
| case ADDRESS_CONST_INT: |
| return msa_p ? 0 : factor; |
| |
| case ADDRESS_SYMBOLIC: |
| return msa_p ? 0 : factor * mips_symbol_insns (addr.symbol_type, mode); |
| } |
| return 0; |
| } |
| |
| /* Return true if X fits within an unsigned field of BITS bits that is |
| shifted left SHIFT bits before being used. */ |
| |
| bool |
| mips_unsigned_immediate_p (unsigned HOST_WIDE_INT x, int bits, int shift = 0) |
| { |
| return (x & ((1 << shift) - 1)) == 0 && x < ((unsigned) 1 << (shift + bits)); |
| } |
| |
| /* Return true if X fits within a signed field of BITS bits that is |
| shifted left SHIFT bits before being used. */ |
| |
| bool |
| mips_signed_immediate_p (unsigned HOST_WIDE_INT x, int bits, int shift = 0) |
| { |
| x += 1 << (bits + shift - 1); |
| return mips_unsigned_immediate_p (x, bits, shift); |
| } |
| |
| /* Return the scale shift that applied to MSA LD/ST address offset. */ |
| |
| int |
| mips_ldst_scaled_shift (machine_mode mode) |
| { |
| int shift = exact_log2 (GET_MODE_UNIT_SIZE (mode)); |
| |
| if (shift < 0 || shift > 8) |
| gcc_unreachable (); |
| |
| return shift; |
| } |
| |
| /* Return true if X is legitimate for accessing values of mode MODE, |
| if it is based on a MIPS16 register, and if the offset satisfies |
| OFFSET_PREDICATE. */ |
| |
| bool |
| m16_based_address_p (rtx x, machine_mode mode, |
| insn_operand_predicate_fn offset_predicate) |
| { |
| struct mips_address_info addr; |
| |
| return (mips_classify_address (&addr, x, mode, false) |
| && addr.type == ADDRESS_REG |
| && M16_REG_P (REGNO (addr.reg)) |
| && offset_predicate (addr.offset, mode)); |
| } |
| |
| /* Return true if X is a legitimate address that conforms to the requirements |
| for a microMIPS LWSP or SWSP insn. */ |
| |
| bool |
| lwsp_swsp_address_p (rtx x, machine_mode mode) |
| { |
| struct mips_address_info addr; |
| |
| return (mips_classify_address (&addr, x, mode, false) |
| && addr.type == ADDRESS_REG |
| && REGNO (addr.reg) == STACK_POINTER_REGNUM |
| && uw5_operand (addr.offset, mode)); |
| } |
| |
| /* Return true if X is a legitimate address with a 12-bit offset. |
| MODE is the mode of the value being accessed. */ |
| |
| bool |
| umips_12bit_offset_address_p (rtx x, machine_mode mode) |
| { |
| struct mips_address_info addr; |
| |
| return (mips_classify_address (&addr, x, mode, false) |
| && addr.type == ADDRESS_REG |
| && CONST_INT_P (addr.offset) |
| && UMIPS_12BIT_OFFSET_P (INTVAL (addr.offset))); |
| } |
| |
| /* Return true if X is a legitimate address with a 9-bit offset. |
| MODE is the mode of the value being accessed. */ |
| |
| bool |
| mips_9bit_offset_address_p (rtx x, machine_mode mode) |
| { |
| struct mips_address_info addr; |
| |
| return (mips_classify_address (&addr, x, mode, false) |
| && addr.type == ADDRESS_REG |
| && CONST_INT_P (addr.offset) |
| && MIPS_9BIT_OFFSET_P (INTVAL (addr.offset))); |
| } |
| |
| /* Return the number of instructions needed to load constant X, |
| assuming that BASE_INSN_LENGTH is the length of one instruction. |
| Return 0 if X isn't a valid constant. */ |
| |
| int |
| mips_const_insns (rtx x) |
| { |
| struct mips_integer_op codes[MIPS_MAX_INTEGER_OPS]; |
| enum mips_symbol_type symbol_type; |
| rtx offset; |
| |
| switch (GET_CODE (x)) |
| { |
| case HIGH: |
| if (!mips_symbolic_constant_p (XEXP (x, 0), SYMBOL_CONTEXT_LEA, |
| &symbol_type) |
| || !mips_split_p[symbol_type]) |
| return 0; |
| |
| /* This is simply an LUI for normal mode. It is an extended |
| LI followed by an extended SLL for MIPS16. */ |
| return TARGET_MIPS16 ? 4 : 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 (IN_RANGE (INTVAL (x), 0, 255) ? 1 |
| : SMALL_OPERAND_UNSIGNED (INTVAL (x)) ? 2 |
| : IN_RANGE (-INTVAL (x), 0, 255) ? 2 |
| : SMALL_OPERAND_UNSIGNED (-INTVAL (x)) ? 3 |
| : 0); |
| |
| return mips_build_integer (codes, INTVAL (x)); |
| |
| case CONST_VECTOR: |
| if (MSA_SUPPORTED_MODE_P (GET_MODE (x)) |
| && mips_const_vector_same_int_p (x, GET_MODE (x), -512, 511)) |
| return 1; |
| /* Fall through. */ |
| case CONST_DOUBLE: |
| /* Allow zeros for normal mode, where we can use $0. */ |
| 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_CONTEXT_LEA, &symbol_type)) |
| return mips_symbol_insns (symbol_type, MAX_MACHINE_MODE); |
| |
| /* Otherwise try splitting the constant into a base and offset. |
| If the offset is a 16-bit value, we can load the base address |
| into a register and then use (D)ADDIU to add in the offset. |
| If the offset is larger, we can load the base and offset |
| into separate registers and add them together with (D)ADDU. |
| However, the latter is only possible before reload; during |
| and after reload, we must have the option of forcing the |
| constant into the pool instead. */ |
| split_const (x, &x, &offset); |
| if (offset != 0) |
| { |
| int n = mips_const_insns (x); |
| if (n != 0) |
| { |
| if (SMALL_INT (offset)) |
| return n + 1; |
| else if (!targetm.cannot_force_const_mem (GET_MODE (x), x)) |
| return n + 1 + mips_build_integer (codes, INTVAL (offset)); |
| } |
| } |
| return 0; |
| |
| case SYMBOL_REF: |
| case LABEL_REF: |
| return mips_symbol_insns (mips_classify_symbol (x, SYMBOL_CONTEXT_LEA), |
| MAX_MACHINE_MODE); |
| |
| default: |
| return 0; |
| } |
| } |
| |
| /* X is a doubleword constant that can be handled by splitting it into |
| two words and loading each word separately. Return the number of |
| instructions required to do this, assuming that BASE_INSN_LENGTH |
| is the length of one instruction. */ |
| |
| int |
| mips_split_const_insns (rtx x) |
| { |
| unsigned int low, high; |
| |
| low = mips_const_insns (mips_subword (x, false)); |
| high = mips_const_insns (mips_subword (x, true)); |
| gcc_assert (low > 0 && high > 0); |
| return low + high; |
| } |
| |
| /* Return one word of 128-bit value OP, taking into account the fixed |
| endianness of certain registers. BYTE selects from the byte address. */ |
| |
| rtx |
| mips_subword_at_byte (rtx op, unsigned int byte) |
| { |
| machine_mode mode; |
| |
| mode = GET_MODE (op); |
| if (mode == VOIDmode) |
| mode = TImode; |
| |
| gcc_assert (!FP_REG_RTX_P (op)); |
| |
| if (MEM_P (op)) |
| return mips_rewrite_small_data (adjust_address (op, word_mode, byte)); |
| |
| return simplify_gen_subreg (word_mode, op, mode, byte); |
| } |
| |
| /* Return the number of instructions needed to implement INSN, |
| given that it loads from or stores to MEM. Assume that |
| BASE_INSN_LENGTH is the length of one instruction. */ |
| |
| int |
| mips_load_store_insns (rtx mem, rtx_insn *insn) |
| { |
| machine_mode mode; |
| bool might_split_p; |
| rtx set; |
| |
| gcc_assert (MEM_P (mem)); |
| mode = GET_MODE (mem); |
| |
| /* Try to prove that INSN does not need to be split. */ |
| might_split_p = GET_MODE_SIZE (mode) > UNITS_PER_WORD; |
| if (might_split_p) |
| { |
| set = single_set (insn); |
| if (set && !mips_split_move_insn_p (SET_DEST (set), SET_SRC (set), insn)) |
| might_split_p = false; |
| } |
| |
| return mips_address_insns (XEXP (mem, 0), mode, might_split_p); |
| } |
| |
| /* Return the number of instructions needed for an integer division, |
| assuming that BASE_INSN_LENGTH is the length of one instruction. */ |
| |
| int |
| mips_idiv_insns (machine_mode mode) |
| { |
| int count; |
| |
| count = 1; |
| if (TARGET_CHECK_ZERO_DIV) |
| { |
| if (GENERATE_DIVIDE_TRAPS && !MSA_SUPPORTED_MODE_P (mode)) |
| count++; |
| else |
| count += 2; |
| } |
| |
| if (TARGET_FIX_R4000 || TARGET_FIX_R4400) |
| count++; |
| return count; |
| } |
| |
| |
| /* Emit a move from SRC to DEST. Assume that the move expanders can |
| handle all moves if !can_create_pseudo_p (). The distinction is |
| important because, unlike emit_move_insn, the move expanders know |
| how to force Pmode objects into the constant pool even when the |
| constant pool address is not itself legitimate. */ |
| |
| rtx_insn * |
| mips_emit_move (rtx dest, rtx src) |
| { |
| return (can_create_pseudo_p () |
| ? emit_move_insn (dest, src) |
| : emit_move_insn_1 (dest, src)); |
| } |
| |
| /* Emit a move from SRC to DEST, splitting compound moves into individual |
| instructions. SPLIT_TYPE is the type of split to perform. */ |
| |
| static void |
| mips_emit_move_or_split (rtx dest, rtx src, enum mips_split_type split_type) |
| { |
| if (mips_split_move_p (dest, src, split_type)) |
| mips_split_move (dest, src, split_type, NULL); |
| else |
| mips_emit_move (dest, src); |
| } |
| |
| /* Emit an instruction of the form (set TARGET (CODE OP0)). */ |
| |
| static void |
| mips_emit_unary (enum rtx_code code, rtx target, rtx op0) |
| { |
| emit_insn (gen_rtx_SET (target, gen_rtx_fmt_e (code, GET_MODE (op0), op0))); |
| } |
| |
| /* Compute (CODE OP0) and store the result in a new register of mode MODE. |
| Return that new register. */ |
| |
| static rtx |
| mips_force_unary (machine_mode mode, enum rtx_code code, rtx op0) |
| { |
| rtx reg; |
| |
| reg = gen_reg_rtx (mode); |
| mips_emit_unary (code, reg, op0); |
| return reg; |
| } |
| |
| /* Emit an instruction of the form (set TARGET (CODE OP0 OP1)). */ |
| |
| void |
| mips_emit_binary (enum rtx_code code, rtx target, rtx op0, rtx op1) |
| { |
| emit_insn (gen_rtx_SET (target, gen_rtx_fmt_ee (code, GET_MODE (target), |
| op0, op1))); |
| } |
| |
| /* Compute (CODE OP0 OP1) and store the result in a new register |
| of mode MODE. Return that new register. */ |
| |
| static rtx |
| mips_force_binary (machine_mode mode, enum rtx_code code, rtx op0, rtx op1) |
| { |
| rtx reg; |
| |
| reg = gen_reg_rtx (mode); |
| mips_emit_binary (code, reg, op0, op1); |
| return reg; |
| } |
| |
| /* Copy VALUE to a register and return that register. If new pseudos |
| are allowed, copy it into a new register, otherwise use DEST. */ |
| |
| static rtx |
| mips_force_temporary (rtx dest, rtx value) |
| { |
| if (can_create_pseudo_p ()) |
| return force_reg (Pmode, value); |
| else |
| { |
| mips_emit_move (dest, value); |
| return dest; |
| } |
| } |
| |
| /* Emit a call sequence with call pattern PATTERN and return the call |
| instruction itself (which is not necessarily the last instruction |
| emitted). ORIG_ADDR is the original, unlegitimized address, |
| ADDR is the legitimized form, and LAZY_P is true if the call |
| address is lazily-bound. */ |
| |
| static rtx_insn * |
| mips_emit_call_insn (rtx pattern, rtx orig_addr, rtx addr, bool lazy_p) |
| { |
| rtx_insn *insn; |
| rtx reg; |
| |
| insn = emit_call_insn (pattern); |
| |
| if (TARGET_MIPS16 && mips_use_pic_fn_addr_reg_p (orig_addr)) |
| { |
| /* MIPS16 JALRs only take MIPS16 registers. If the target |
| function requires $25 to be valid on entry, we must copy it |
| there separately. The move instruction can be put in the |
| call's delay slot. */ |
| reg = gen_rtx_REG (Pmode, PIC_FUNCTION_ADDR_REGNUM); |
| emit_insn_before (gen_move_insn (reg, addr), insn); |
| use_reg (&CALL_INSN_FUNCTION_USAGE (insn), reg); |
| } |
| |
| if (lazy_p) |
| /* Lazy-binding stubs require $gp to be valid on entry. */ |
| use_reg (&CALL_INSN_FUNCTION_USAGE (insn), pic_offset_table_rtx); |
| |
| if (TARGET_USE_GOT) |
| { |
| /* See the comment above load_call<mode> for details. */ |
| use_reg (&CALL_INSN_FUNCTION_USAGE (insn), |
| gen_rtx_REG (Pmode, GOT_VERSION_REGNUM)); |
| emit_insn (gen_update_got_version ()); |
| } |
| |
| if (TARGET_MIPS16 |
| && TARGET_EXPLICIT_RELOCS |
| && TARGET_CALL_CLOBBERED_GP) |
| { |
| rtx post_call_tmp_reg = gen_rtx_REG (word_mode, POST_CALL_TMP_REG); |
| clobber_reg (&CALL_INSN_FUNCTION_USAGE (insn), post_call_tmp_reg); |
| } |
| |
| return insn; |
| } |
| |
| /* Wrap symbol or label BASE in an UNSPEC address of type SYMBOL_TYPE, |
| then add CONST_INT OFFSET to the result. */ |
| |
| static rtx |
| mips_unspec_address_offset (rtx base, rtx offset, |
| enum mips_symbol_type symbol_type) |
| { |
| base = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, base), |
| UNSPEC_ADDRESS_FIRST + symbol_type); |
| if (offset != const0_rtx) |
| base = gen_rtx_PLUS (Pmode, base, offset); |
| return gen_rtx_CONST (Pmode, base); |
| } |
| |
| /* Return an UNSPEC address with underlying address ADDRESS and symbol |
| type SYMBOL_TYPE. */ |
| |
| rtx |
| mips_unspec_address (rtx address, enum mips_symbol_type symbol_type) |
| { |
| rtx base, offset; |
| |
| split_const (address, &base, &offset); |
| return mips_unspec_address_offset (base, offset, symbol_type); |
| } |
| |
| /* If OP is an UNSPEC address, return the address to which it refers, |
| otherwise return OP itself. */ |
| |
| rtx |
| mips_strip_unspec_address (rtx op) |
| { |
| rtx base, offset; |
| |
| split_const (op, &base, &offset); |
| if (UNSPEC_ADDRESS_P (base)) |
| op = plus_constant (Pmode, UNSPEC_ADDRESS (base), INTVAL (offset)); |
| return op; |
| } |
| |
| /* 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 as for mips_force_temporary. |
| |
| 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); |
| base = mips_force_temporary (temp, gen_rtx_PLUS (Pmode, addr, base)); |
| } |
| return base; |
| } |
| |
| /* Return an instruction that copies $gp into register REG. We want |
| GCC to treat the register's value as constant, so that its value |
| can be rematerialized on demand. */ |
| |
| static rtx |
| gen_load_const_gp (rtx reg) |
| { |
| return PMODE_INSN (gen_load_const_gp, (reg)); |
| } |
| |
| /* Return a pseudo register that contains the value of $gp throughout |
| the current function. Such registers are needed by MIPS16 functions, |
| for which $gp itself is not a valid base register or addition operand. */ |
| |
| static rtx |
| mips16_gp_pseudo_reg (void) |
| { |
| if (cfun->machine->mips16_gp_pseudo_rtx == NULL_RTX) |
| { |
| rtx_insn *scan; |
| |
| cfun->machine->mips16_gp_pseudo_rtx = gen_reg_rtx (Pmode); |
| |
| push_topmost_sequence (); |
| |
| scan = get_insns (); |
| while (NEXT_INSN (scan) && !INSN_P (NEXT_INSN (scan))) |
| scan = NEXT_INSN (scan); |
| |
| rtx set = gen_load_const_gp (cfun->machine->mips16_gp_pseudo_rtx); |
| rtx_insn *insn = emit_insn_after (set, scan); |
| INSN_LOCATION (insn) = 0; |
| |
| pop_topmost_sequence (); |
| } |
| |
| return cfun->machine->mips16_gp_pseudo_rtx; |
| } |
| |
| /* Return a base register that holds pic_offset_table_rtx. |
| TEMP, if nonnull, is a scratch Pmode base register. */ |
| |
| rtx |
| mips_pic_base_register (rtx temp) |
| { |
| if (!TARGET_MIPS16) |
| return pic_offset_table_rtx; |
| |
|