| /* Subroutines for insn-output.c for MIPS |
| Copyright (C) 1989, 90, 91, 93-95, 1996 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 GNU CC. |
| |
| GNU CC is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU CC 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 GNU CC; see the file COPYING. If not, write to |
| the Free Software Foundation, 59 Temple Place - Suite 330, |
| Boston, MA 02111-1307, USA. */ |
| |
| /* ??? The TARGET_FP_CALL_32 macros are intended to simulate a 32 bit |
| calling convention in 64 bit mode. It doesn't work though, and should |
| be replaced with something better designed. */ |
| |
| #include "config.h" |
| #include "rtl.h" |
| #include "regs.h" |
| #include "hard-reg-set.h" |
| #include "real.h" |
| #include "insn-config.h" |
| #include "conditions.h" |
| #include "insn-flags.h" |
| #include "insn-attr.h" |
| #include "insn-codes.h" |
| #include "recog.h" |
| #include "output.h" |
| |
| #undef MAX /* sys/param.h may also define these */ |
| #undef MIN |
| |
| #include <stdio.h> |
| #include <signal.h> |
| #include <sys/types.h> |
| #include <sys/file.h> |
| #include <ctype.h> |
| #include "tree.h" |
| #include "expr.h" |
| #include "flags.h" |
| |
| #ifndef R_OK |
| #define R_OK 4 |
| #define W_OK 2 |
| #define X_OK 1 |
| #endif |
| |
| #if defined(USG) || defined(NO_STAB_H) |
| #include "gstab.h" /* If doing DBX on sysV, use our own stab.h. */ |
| #else |
| #include <stab.h> /* On BSD, use the system's stab.h. */ |
| #endif /* not USG */ |
| |
| #ifdef __GNU_STAB__ |
| #define STAB_CODE_TYPE enum __stab_debug_code |
| #else |
| #define STAB_CODE_TYPE int |
| #endif |
| |
| extern void abort (); |
| extern int atoi (); |
| extern char *getenv (); |
| extern char *mktemp (); |
| |
| extern rtx adj_offsettable_operand (); |
| extern rtx copy_to_reg (); |
| extern void error (); |
| extern void fatal (); |
| extern tree lookup_name (); |
| extern void pfatal_with_name (); |
| extern void warning (); |
| |
| extern FILE *asm_out_file; |
| |
| /* Enumeration for all of the relational tests, so that we can build |
| arrays indexed by the test type, and not worry about the order |
| of EQ, NE, etc. */ |
| |
| enum internal_test { |
| ITEST_EQ, |
| ITEST_NE, |
| ITEST_GT, |
| ITEST_GE, |
| ITEST_LT, |
| ITEST_LE, |
| ITEST_GTU, |
| ITEST_GEU, |
| ITEST_LTU, |
| ITEST_LEU, |
| ITEST_MAX |
| }; |
| |
| /* Global variables for machine-dependent things. */ |
| |
| /* Threshold for data being put into the small data/bss area, instead |
| of the normal data area (references to the small data/bss area take |
| 1 instruction, and use the global pointer, references to the normal |
| data area takes 2 instructions). */ |
| int mips_section_threshold = -1; |
| |
| /* Count the number of .file directives, so that .loc is up to date. */ |
| int num_source_filenames = 0; |
| |
| /* Count the number of sdb related labels are generated (to find block |
| start and end boundaries). */ |
| int sdb_label_count = 0; |
| |
| /* Next label # for each statement for Silicon Graphics IRIS systems. */ |
| int sym_lineno = 0; |
| |
| /* Non-zero if inside of a function, because the stupid MIPS asm can't |
| handle .files inside of functions. */ |
| int inside_function = 0; |
| |
| /* Files to separate the text and the data output, so that all of the data |
| can be emitted before the text, which will mean that the assembler will |
| generate smaller code, based on the global pointer. */ |
| FILE *asm_out_data_file; |
| FILE *asm_out_text_file; |
| |
| /* Linked list of all externals that are to be emitted when optimizing |
| for the global pointer if they haven't been declared by the end of |
| the program with an appropriate .comm or initialization. */ |
| |
| struct extern_list { |
| struct extern_list *next; /* next external */ |
| char *name; /* name of the external */ |
| int size; /* size in bytes */ |
| } *extern_head = 0; |
| |
| /* Name of the file containing the current function. */ |
| char *current_function_file = ""; |
| |
| /* Warning given that Mips ECOFF can't support changing files |
| within a function. */ |
| int file_in_function_warning = FALSE; |
| |
| /* Whether to suppress issuing .loc's because the user attempted |
| to change the filename within a function. */ |
| int ignore_line_number = FALSE; |
| |
| /* Number of nested .set noreorder, noat, nomacro, and volatile requests. */ |
| int set_noreorder; |
| int set_noat; |
| int set_nomacro; |
| int set_volatile; |
| |
| /* The next branch instruction is a branch likely, not branch normal. */ |
| int mips_branch_likely; |
| |
| /* Count of delay slots and how many are filled. */ |
| int dslots_load_total; |
| int dslots_load_filled; |
| int dslots_jump_total; |
| int dslots_jump_filled; |
| |
| /* # of nops needed by previous insn */ |
| int dslots_number_nops; |
| |
| /* Number of 1/2/3 word references to data items (ie, not jal's). */ |
| int num_refs[3]; |
| |
| /* registers to check for load delay */ |
| rtx mips_load_reg, mips_load_reg2, mips_load_reg3, mips_load_reg4; |
| |
| /* Cached operands, and operator to compare for use in set/branch on |
| condition codes. */ |
| rtx branch_cmp[2]; |
| |
| /* what type of branch to use */ |
| enum cmp_type branch_type; |
| |
| /* Number of previously seen half-pic pointers and references. */ |
| static int prev_half_pic_ptrs = 0; |
| static int prev_half_pic_refs = 0; |
| |
| /* which cpu are we scheduling for */ |
| enum processor_type mips_cpu; |
| |
| /* which instruction set architecture to use. */ |
| int mips_isa; |
| |
| #ifdef MIPS_ABI_DEFAULT |
| /* which ABI to use. This is defined to a constant in mips.h if the target |
| doesn't support multiple ABIs. */ |
| enum mips_abi_type mips_abi; |
| #endif |
| |
| /* Strings to hold which cpu and instruction set architecture to use. */ |
| char *mips_cpu_string; /* for -mcpu=<xxx> */ |
| char *mips_isa_string; /* for -mips{1,2,3,4} */ |
| char *mips_abi_string; /* for -mabi={o32,32,n32,n64,64,eabi} */ |
| |
| /* If TRUE, we split addresses into their high and low parts in the RTL. */ |
| int mips_split_addresses; |
| |
| /* Generating calls to position independent functions? */ |
| enum mips_abicalls_type mips_abicalls; |
| |
| /* High and low marks for floating point values which we will accept |
| as legitimate constants for LEGITIMATE_CONSTANT_P. These are |
| initialized in override_options. */ |
| REAL_VALUE_TYPE dfhigh, dflow, sfhigh, sflow; |
| |
| /* Array giving truth value on whether or not a given hard register |
| can support a given mode. */ |
| char mips_hard_regno_mode_ok[(int)MAX_MACHINE_MODE][FIRST_PSEUDO_REGISTER]; |
| |
| /* Current frame information calculated by compute_frame_size. */ |
| struct mips_frame_info current_frame_info; |
| |
| /* Zero structure to initialize current_frame_info. */ |
| struct mips_frame_info zero_frame_info; |
| |
| /* Temporary filename used to buffer .text until end of program |
| for -mgpopt. */ |
| static char *temp_filename; |
| |
| /* Pseudo-reg holding the address of the current function when |
| generating embedded PIC code. Created by LEGITIMIZE_ADDRESS, used |
| by mips_finalize_pic if it was created. */ |
| rtx embedded_pic_fnaddr_rtx; |
| |
| /* List of all MIPS punctuation characters used by print_operand. */ |
| char mips_print_operand_punct[256]; |
| |
| /* Map GCC register number to debugger register number. */ |
| int mips_dbx_regno[FIRST_PSEUDO_REGISTER]; |
| |
| /* Buffer to use to enclose a load/store operation with %{ %} to |
| turn on .set volatile. */ |
| static char volatile_buffer[60]; |
| |
| /* Hardware names for the registers. If -mrnames is used, this |
| will be overwritten with mips_sw_reg_names. */ |
| |
| char mips_reg_names[][8] = |
| { |
| "$0", "$1", "$2", "$3", "$4", "$5", "$6", "$7", |
| "$8", "$9", "$10", "$11", "$12", "$13", "$14", "$15", |
| "$16", "$17", "$18", "$19", "$20", "$21", "$22", "$23", |
| "$24", "$25", "$26", "$27", "$28", "$sp", "$fp", "$31", |
| "$f0", "$f1", "$f2", "$f3", "$f4", "$f5", "$f6", "$f7", |
| "$f8", "$f9", "$f10", "$f11", "$f12", "$f13", "$f14", "$f15", |
| "$f16", "$f17", "$f18", "$f19", "$f20", "$f21", "$f22", "$f23", |
| "$f24", "$f25", "$f26", "$f27", "$f28", "$f29", "$f30", "$f31", |
| "hi", "lo", "accum","$fcc0","$fcc1","$fcc2","$fcc3","$fcc4", |
| "$fcc5","$fcc6","$fcc7","$rap" |
| }; |
| |
| /* Mips software names for the registers, used to overwrite the |
| mips_reg_names array. */ |
| |
| char mips_sw_reg_names[][8] = |
| { |
| "$zero","$at", "$v0", "$v1", "$a0", "$a1", "$a2", "$a3", |
| "$t0", "$t1", "$t2", "$t3", "$t4", "$t5", "$t6", "$t7", |
| "$s0", "$s1", "$s2", "$s3", "$s4", "$s5", "$s6", "$s7", |
| "$t8", "$t9", "$k0", "$k1", "$gp", "$sp", "$fp", "$ra", |
| "$f0", "$f1", "$f2", "$f3", "$f4", "$f5", "$f6", "$f7", |
| "$f8", "$f9", "$f10", "$f11", "$f12", "$f13", "$f14", "$f15", |
| "$f16", "$f17", "$f18", "$f19", "$f20", "$f21", "$f22", "$f23", |
| "$f24", "$f25", "$f26", "$f27", "$f28", "$f29", "$f30", "$f31", |
| "hi", "lo", "accum","$fcc0","$fcc1","$fcc2","$fcc3","$fcc4", |
| "$fcc5","$fcc6","$fcc7","$rap" |
| }; |
| |
| /* Map hard register number to register class */ |
| enum reg_class mips_regno_to_class[] = |
| { |
| GR_REGS, GR_REGS, GR_REGS, GR_REGS, |
| GR_REGS, GR_REGS, GR_REGS, GR_REGS, |
| GR_REGS, GR_REGS, GR_REGS, GR_REGS, |
| GR_REGS, GR_REGS, GR_REGS, GR_REGS, |
| GR_REGS, GR_REGS, GR_REGS, GR_REGS, |
| GR_REGS, GR_REGS, GR_REGS, GR_REGS, |
| GR_REGS, GR_REGS, GR_REGS, GR_REGS, |
| GR_REGS, GR_REGS, GR_REGS, GR_REGS, |
| FP_REGS, FP_REGS, FP_REGS, FP_REGS, |
| FP_REGS, FP_REGS, FP_REGS, FP_REGS, |
| FP_REGS, FP_REGS, FP_REGS, FP_REGS, |
| FP_REGS, FP_REGS, FP_REGS, FP_REGS, |
| FP_REGS, FP_REGS, FP_REGS, FP_REGS, |
| FP_REGS, FP_REGS, FP_REGS, FP_REGS, |
| FP_REGS, FP_REGS, FP_REGS, FP_REGS, |
| FP_REGS, FP_REGS, FP_REGS, FP_REGS, |
| HI_REG, LO_REG, HILO_REG, ST_REGS, |
| ST_REGS, ST_REGS, ST_REGS, ST_REGS, |
| ST_REGS, ST_REGS, ST_REGS, GR_REGS |
| }; |
| |
| /* Map register constraint character to register class. */ |
| enum reg_class mips_char_to_class[256] = |
| { |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| NO_REGS, NO_REGS, NO_REGS, NO_REGS, |
| }; |
| |
| |
| /* Return truth value of whether OP can be used as an operands |
| where a register or 16 bit unsigned integer is needed. */ |
| |
| int |
| uns_arith_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| if (GET_CODE (op) == CONST_INT && SMALL_INT_UNSIGNED (op)) |
| return TRUE; |
| |
| return register_operand (op, mode); |
| } |
| |
| /* Return truth value of whether OP can be used as an operands |
| where a 16 bit integer is needed */ |
| |
| int |
| arith_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| if (GET_CODE (op) == CONST_INT && SMALL_INT (op)) |
| return TRUE; |
| |
| return register_operand (op, mode); |
| } |
| |
| /* Return truth value of whether OP can be used as an operand in a two |
| address arithmetic insn (such as set 123456,%o4) of mode MODE. */ |
| |
| int |
| arith32_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| if (GET_CODE (op) == CONST_INT) |
| return TRUE; |
| |
| return register_operand (op, mode); |
| } |
| |
| /* Return truth value of whether OP is a integer which fits in 16 bits */ |
| |
| int |
| small_int (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| return (GET_CODE (op) == CONST_INT && SMALL_INT (op)); |
| } |
| |
| /* Return truth value of whether OP is a 32 bit integer which is too big to |
| be loaded with one instruction. */ |
| |
| int |
| large_int (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| HOST_WIDE_INT value; |
| |
| if (GET_CODE (op) != CONST_INT) |
| return FALSE; |
| |
| value = INTVAL (op); |
| if ((value & ~0x0000ffff) == 0) /* ior reg,$r0,value */ |
| return FALSE; |
| |
| if (((unsigned long)(value + 32768)) <= 32767) /* subu reg,$r0,value */ |
| return FALSE; |
| |
| if ((value & 0x0000ffff) == 0) /* lui reg,value>>16 */ |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| /* Return truth value of whether OP is a register or the constant 0. */ |
| |
| int |
| reg_or_0_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| switch (GET_CODE (op)) |
| { |
| default: |
| break; |
| |
| case CONST_INT: |
| return (INTVAL (op) == 0); |
| |
| case CONST_DOUBLE: |
| if (op != CONST0_RTX (mode)) |
| return FALSE; |
| |
| return TRUE; |
| |
| case REG: |
| case SUBREG: |
| return register_operand (op, mode); |
| } |
| |
| return FALSE; |
| } |
| |
| /* Return truth value if a CONST_DOUBLE is ok to be a legitimate constant. */ |
| |
| int |
| mips_const_double_ok (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| REAL_VALUE_TYPE d; |
| |
| if (GET_CODE (op) != CONST_DOUBLE) |
| return FALSE; |
| |
| if (mode == VOIDmode) |
| return TRUE; |
| |
| if (mode != SFmode && mode != DFmode) |
| return FALSE; |
| |
| if (op == CONST0_RTX (mode)) |
| return TRUE; |
| |
| /* ??? li.s does not work right with SGI's Irix 6 assembler. */ |
| if (mips_abi != ABI_32 && mips_abi != ABI_EABI) |
| return FALSE; |
| |
| REAL_VALUE_FROM_CONST_DOUBLE (d, op); |
| |
| if (REAL_VALUE_ISNAN (d)) |
| return FALSE; |
| |
| if (REAL_VALUE_NEGATIVE (d)) |
| d = REAL_VALUE_NEGATE (d); |
| |
| if (mode == DFmode) |
| { |
| if (REAL_VALUES_LESS (d, dfhigh) |
| && REAL_VALUES_LESS (dflow, d)) |
| return TRUE; |
| } |
| else |
| { |
| if (REAL_VALUES_LESS (d, sfhigh) |
| && REAL_VALUES_LESS (sflow, d)) |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| /* Accept the floating point constant 1 in the appropriate mode. */ |
| |
| int |
| const_float_1_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| REAL_VALUE_TYPE d; |
| static REAL_VALUE_TYPE onedf; |
| static REAL_VALUE_TYPE onesf; |
| static int one_initialized; |
| |
| if (GET_CODE (op) != CONST_DOUBLE |
| || mode != GET_MODE (op) |
| || (mode != DFmode && mode != SFmode)) |
| return FALSE; |
| |
| REAL_VALUE_FROM_CONST_DOUBLE (d, op); |
| |
| /* We only initialize these values if we need them, since we will |
| never get called unless mips_isa >= 4. */ |
| if (! one_initialized) |
| { |
| onedf = REAL_VALUE_ATOF ("1.0", DFmode); |
| onesf = REAL_VALUE_ATOF ("1.0", SFmode); |
| one_initialized = TRUE; |
| } |
| |
| if (mode == DFmode) |
| return REAL_VALUES_EQUAL (d, onedf); |
| else |
| return REAL_VALUES_EQUAL (d, onesf); |
| } |
| |
| /* Return truth value if a memory operand fits in a single instruction |
| (ie, register + small offset). */ |
| |
| int |
| simple_memory_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| rtx addr, plus0, plus1; |
| |
| /* Eliminate non-memory operations */ |
| if (GET_CODE (op) != MEM) |
| return FALSE; |
| |
| /* dword operations really put out 2 instructions, so eliminate them. */ |
| /* ??? This isn't strictly correct. It is OK to accept multiword modes |
| here, since the length attributes are being set correctly, but only |
| if the address is offsettable. LO_SUM is not offsettable. */ |
| if (GET_MODE_SIZE (GET_MODE (op)) > UNITS_PER_WORD) |
| return FALSE; |
| |
| /* Decode the address now. */ |
| addr = XEXP (op, 0); |
| switch (GET_CODE (addr)) |
| { |
| default: |
| break; |
| |
| case REG: |
| case LO_SUM: |
| return TRUE; |
| |
| case CONST_INT: |
| return SMALL_INT (op); |
| |
| case PLUS: |
| plus0 = XEXP (addr, 0); |
| plus1 = XEXP (addr, 1); |
| if (GET_CODE (plus0) == REG |
| && GET_CODE (plus1) == CONST_INT |
| && SMALL_INT (plus1)) |
| return TRUE; |
| |
| else if (GET_CODE (plus1) == REG |
| && GET_CODE (plus0) == CONST_INT |
| && SMALL_INT (plus0)) |
| return TRUE; |
| |
| else |
| return FALSE; |
| |
| #if 0 |
| /* We used to allow small symbol refs here (ie, stuff in .sdata |
| or .sbss), but this causes some bugs in G++. Also, it won't |
| interfere if the MIPS linker rewrites the store instruction |
| because the function is PIC. */ |
| |
| case LABEL_REF: /* never gp relative */ |
| break; |
| |
| case CONST: |
| /* If -G 0, we can never have a GP relative memory operation. |
| Also, save some time if not optimizing. */ |
| if (!TARGET_GP_OPT) |
| return FALSE; |
| |
| { |
| rtx offset = const0_rtx; |
| addr = eliminate_constant_term (XEXP (addr, 0), &offset); |
| if (GET_CODE (op) != SYMBOL_REF) |
| return FALSE; |
| |
| /* let's be paranoid.... */ |
| if (! SMALL_INT (offset)) |
| return FALSE; |
| } |
| /* fall through */ |
| |
| case SYMBOL_REF: |
| return SYMBOL_REF_FLAG (addr); |
| #endif |
| } |
| |
| return FALSE; |
| } |
| |
| /* Return true if the code of this rtx pattern is EQ or NE. */ |
| |
| int |
| equality_op (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| if (mode != GET_MODE (op)) |
| return FALSE; |
| |
| return (GET_CODE (op) == EQ || GET_CODE (op) == NE); |
| } |
| |
| /* Return true if the code is a relational operations (EQ, LE, etc.) */ |
| |
| int |
| cmp_op (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| if (mode != GET_MODE (op)) |
| return FALSE; |
| |
| return (GET_RTX_CLASS (GET_CODE (op)) == '<'); |
| } |
| |
| /* Return true if the operand is either the PC or a label_ref. */ |
| |
| int |
| pc_or_label_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| if (op == pc_rtx) |
| return TRUE; |
| |
| if (GET_CODE (op) == LABEL_REF) |
| return TRUE; |
| |
| return FALSE; |
| } |
| |
| /* Test for a valid operand for a call instruction. |
| Don't allow the arg pointer register or virtual regs |
| since they may change into reg + const, which the patterns |
| can't handle yet. */ |
| |
| int |
| call_insn_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| if (CONSTANT_ADDRESS_P (op) |
| || (GET_CODE (op) == REG && op != arg_pointer_rtx |
| && ! (REGNO (op) >= FIRST_PSEUDO_REGISTER |
| && REGNO (op) <= LAST_VIRTUAL_REGISTER))) |
| return 1; |
| return 0; |
| } |
| |
| /* Return true if OPERAND is valid as a source operand for a move |
| instruction. */ |
| |
| int |
| move_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| /* Accept any general operand after reload has started; doing so |
| avoids losing if reload does an in-place replacement of a register |
| with a SYMBOL_REF or CONST. */ |
| return (general_operand (op, mode) |
| && (! (mips_split_addresses && mips_check_split (op, mode)) |
| || reload_in_progress |
| || reload_completed)); |
| |
| |
| } |
| |
| /* Return true if OPERAND is valid as a source operand for movdi. |
| This accepts not only general_operand, but also sign extended |
| constants and registers. We need to accept sign extended constants |
| in case a sign extended register which is used in an expression, |
| and is equivalent to a constant, is spilled. */ |
| |
| int |
| movdi_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| if (TARGET_64BIT |
| && mode == DImode |
| && GET_CODE (op) == SIGN_EXTEND |
| && GET_MODE (op) == DImode |
| && (GET_MODE (XEXP (op, 0)) == SImode |
| || (GET_CODE (XEXP (op, 0)) == CONST_INT |
| && GET_MODE (XEXP (op, 0)) == VOIDmode)) |
| && (register_operand (XEXP (op, 0), SImode) |
| || immediate_operand (XEXP (op, 0), SImode))) |
| return 1; |
| |
| return general_operand (op, mode); |
| } |
| |
| /* Like register_operand, but when in 64 bit mode also accept a sign |
| extend of a 32 bit register, since the value is known to be already |
| sign extended. */ |
| |
| int |
| se_register_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| if (TARGET_64BIT |
| && mode == DImode |
| && GET_CODE (op) == SIGN_EXTEND |
| && GET_MODE (op) == DImode |
| && GET_MODE (XEXP (op, 0)) == SImode |
| && register_operand (XEXP (op, 0), SImode)) |
| return 1; |
| |
| return register_operand (op, mode); |
| } |
| |
| /* Like reg_or_0_operand, but when in 64 bit mode also accept a sign |
| extend of a 32 bit register, since the value is known to be already |
| sign extended. */ |
| |
| int |
| se_reg_or_0_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| if (TARGET_64BIT |
| && mode == DImode |
| && GET_CODE (op) == SIGN_EXTEND |
| && GET_MODE (op) == DImode |
| && GET_MODE (XEXP (op, 0)) == SImode |
| && register_operand (XEXP (op, 0), SImode)) |
| return 1; |
| |
| return reg_or_0_operand (op, mode); |
| } |
| |
| /* Like uns_arith_operand, but when in 64 bit mode also accept a sign |
| extend of a 32 bit register, since the value is known to be already |
| sign extended. */ |
| |
| int |
| se_uns_arith_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| if (TARGET_64BIT |
| && mode == DImode |
| && GET_CODE (op) == SIGN_EXTEND |
| && GET_MODE (op) == DImode |
| && GET_MODE (XEXP (op, 0)) == SImode |
| && register_operand (XEXP (op, 0), SImode)) |
| return 1; |
| |
| return uns_arith_operand (op, mode); |
| } |
| |
| /* Like arith_operand, but when in 64 bit mode also accept a sign |
| extend of a 32 bit register, since the value is known to be already |
| sign extended. */ |
| |
| int |
| se_arith_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| if (TARGET_64BIT |
| && mode == DImode |
| && GET_CODE (op) == SIGN_EXTEND |
| && GET_MODE (op) == DImode |
| && GET_MODE (XEXP (op, 0)) == SImode |
| && register_operand (XEXP (op, 0), SImode)) |
| return 1; |
| |
| return arith_operand (op, mode); |
| } |
| |
| /* Like nonmemory_operand, but when in 64 bit mode also accept a sign |
| extend of a 32 bit register, since the value is known to be already |
| sign extended. */ |
| |
| int |
| se_nonmemory_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| if (TARGET_64BIT |
| && mode == DImode |
| && GET_CODE (op) == SIGN_EXTEND |
| && GET_MODE (op) == DImode |
| && GET_MODE (XEXP (op, 0)) == SImode |
| && register_operand (XEXP (op, 0), SImode)) |
| return 1; |
| |
| return nonmemory_operand (op, mode); |
| } |
| |
| /* Like nonimmediate_operand, but when in 64 bit mode also accept a |
| sign extend of a 32 bit register, since the value is known to be |
| already sign extended. */ |
| |
| int |
| se_nonimmediate_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| if (TARGET_64BIT |
| && mode == DImode |
| && GET_CODE (op) == SIGN_EXTEND |
| && GET_MODE (op) == DImode |
| && GET_MODE (XEXP (op, 0)) == SImode |
| && register_operand (XEXP (op, 0), SImode)) |
| return 1; |
| |
| return nonimmediate_operand (op, mode); |
| } |
| |
| /* Return true if we split the address into high and low parts. */ |
| |
| /* ??? We should also handle reg+array somewhere. We get four |
| instructions currently, lui %hi/addui %lo/addui reg/lw. Better is |
| lui %hi/addui reg/lw %lo. Fixing GO_IF_LEGITIMATE_ADDRESS to accept |
| (plus (reg) (symbol_ref)) doesn't work because the SYMBOL_REF is broken |
| out of the address, then we have 4 instructions to combine. Perhaps |
| add a 3->2 define_split for combine. */ |
| |
| /* ??? We could also split a CONST_INT here if it is a large_int(). |
| However, it doesn't seem to be very useful to have %hi(constant). |
| We would be better off by doing the masking ourselves and then putting |
| the explicit high part of the constant in the RTL. This will give better |
| optimization. Also, %hi(constant) needs assembler changes to work. |
| There is already a define_split that does this. */ |
| |
| int |
| mips_check_split (address, mode) |
| rtx address; |
| enum machine_mode mode; |
| { |
| /* ??? This is the same check used in simple_memory_operand. |
| We use it here because LO_SUM is not offsettable. */ |
| if (GET_MODE_SIZE (mode) > UNITS_PER_WORD) |
| return 0; |
| |
| if ((GET_CODE (address) == SYMBOL_REF && ! SYMBOL_REF_FLAG (address)) |
| || (GET_CODE (address) == CONST |
| && GET_CODE (XEXP (XEXP (address, 0), 0)) == SYMBOL_REF |
| && ! SYMBOL_REF_FLAG (XEXP (XEXP (address, 0), 0))) |
| || GET_CODE (address) == LABEL_REF) |
| return 1; |
| |
| return 0; |
| } |
| |
| /* Returns an operand string for the given instruction's delay slot, |
| after updating filled delay slot statistics. |
| |
| We assume that operands[0] is the target register that is set. |
| |
| In order to check the next insn, most of this functionality is moved |
| to FINAL_PRESCAN_INSN, and we just set the global variables that |
| it needs. */ |
| |
| /* ??? This function no longer does anything useful, because final_prescan_insn |
| now will never emit a nop. */ |
| |
| char * |
| mips_fill_delay_slot (ret, type, operands, cur_insn) |
| char *ret; /* normal string to return */ |
| enum delay_type type; /* type of delay */ |
| rtx operands[]; /* operands to use */ |
| rtx cur_insn; /* current insn */ |
| { |
| register rtx set_reg; |
| register enum machine_mode mode; |
| register rtx next_insn = (cur_insn) ? NEXT_INSN (cur_insn) : (rtx)0; |
| register int num_nops; |
| |
| if (type == DELAY_LOAD || type == DELAY_FCMP) |
| num_nops = 1; |
| |
| else if (type == DELAY_HILO) |
| num_nops = 2; |
| |
| else |
| num_nops = 0; |
| |
| /* Make sure that we don't put nop's after labels. */ |
| next_insn = NEXT_INSN (cur_insn); |
| while (next_insn != (rtx)0 && GET_CODE (next_insn) == NOTE) |
| next_insn = NEXT_INSN (next_insn); |
| |
| dslots_load_total += num_nops; |
| if (TARGET_DEBUG_F_MODE |
| || !optimize |
| || type == DELAY_NONE |
| || operands == (rtx *)0 |
| || cur_insn == (rtx)0 |
| || next_insn == (rtx)0 |
| || GET_CODE (next_insn) == CODE_LABEL |
| || (set_reg = operands[0]) == (rtx)0) |
| { |
| dslots_number_nops = 0; |
| mips_load_reg = (rtx)0; |
| mips_load_reg2 = (rtx)0; |
| mips_load_reg3 = (rtx)0; |
| mips_load_reg4 = (rtx)0; |
| return ret; |
| } |
| |
| set_reg = operands[0]; |
| if (set_reg == (rtx)0) |
| return ret; |
| |
| while (GET_CODE (set_reg) == SUBREG) |
| set_reg = SUBREG_REG (set_reg); |
| |
| mode = GET_MODE (set_reg); |
| dslots_number_nops = num_nops; |
| mips_load_reg = set_reg; |
| if (GET_MODE_SIZE (mode) |
| > (FP_REG_P (REGNO (set_reg)) ? UNITS_PER_FPREG : UNITS_PER_WORD)) |
| mips_load_reg2 = gen_rtx (REG, SImode, REGNO (set_reg) + 1); |
| else |
| mips_load_reg2 = 0; |
| |
| if (type == DELAY_HILO) |
| { |
| mips_load_reg3 = gen_rtx (REG, SImode, MD_REG_FIRST); |
| mips_load_reg4 = gen_rtx (REG, SImode, MD_REG_FIRST+1); |
| } |
| else |
| { |
| mips_load_reg3 = 0; |
| mips_load_reg4 = 0; |
| } |
| |
| return ret; |
| } |
| |
| |
| /* Determine whether a memory reference takes one (based off of the GP pointer), |
| two (normal), or three (label + reg) instructions, and bump the appropriate |
| counter for -mstats. */ |
| |
| void |
| mips_count_memory_refs (op, num) |
| rtx op; |
| int num; |
| { |
| int additional = 0; |
| int n_words = 0; |
| rtx addr, plus0, plus1; |
| enum rtx_code code0, code1; |
| int looping; |
| |
| if (TARGET_DEBUG_B_MODE) |
| { |
| fprintf (stderr, "\n========== mips_count_memory_refs:\n"); |
| debug_rtx (op); |
| } |
| |
| /* Skip MEM if passed, otherwise handle movsi of address. */ |
| addr = (GET_CODE (op) != MEM) ? op : XEXP (op, 0); |
| |
| /* Loop, going through the address RTL */ |
| do |
| { |
| looping = FALSE; |
| switch (GET_CODE (addr)) |
| { |
| default: |
| break; |
| |
| case REG: |
| case CONST_INT: |
| case LO_SUM: |
| break; |
| |
| case PLUS: |
| plus0 = XEXP (addr, 0); |
| plus1 = XEXP (addr, 1); |
| code0 = GET_CODE (plus0); |
| code1 = GET_CODE (plus1); |
| |
| if (code0 == REG) |
| { |
| additional++; |
| addr = plus1; |
| looping = TRUE; |
| continue; |
| } |
| |
| if (code0 == CONST_INT) |
| { |
| addr = plus1; |
| looping = TRUE; |
| continue; |
| } |
| |
| if (code1 == REG) |
| { |
| additional++; |
| addr = plus0; |
| looping = TRUE; |
| continue; |
| } |
| |
| if (code1 == CONST_INT) |
| { |
| addr = plus0; |
| looping = TRUE; |
| continue; |
| } |
| |
| if (code0 == SYMBOL_REF || code0 == LABEL_REF || code0 == CONST) |
| { |
| addr = plus0; |
| looping = TRUE; |
| continue; |
| } |
| |
| if (code1 == SYMBOL_REF || code1 == LABEL_REF || code1 == CONST) |
| { |
| addr = plus1; |
| looping = TRUE; |
| continue; |
| } |
| |
| break; |
| |
| case LABEL_REF: |
| n_words = 2; /* always 2 words */ |
| break; |
| |
| case CONST: |
| addr = XEXP (addr, 0); |
| looping = TRUE; |
| continue; |
| |
| case SYMBOL_REF: |
| n_words = SYMBOL_REF_FLAG (addr) ? 1 : 2; |
| break; |
| } |
| } |
| while (looping); |
| |
| if (n_words == 0) |
| return; |
| |
| n_words += additional; |
| if (n_words > 3) |
| n_words = 3; |
| |
| num_refs[n_words-1] += num; |
| } |
| |
| |
| /* Return RTL for the offset from the current function to the |
| argument. */ |
| |
| rtx |
| embedded_pic_offset (x) |
| rtx x; |
| { |
| if (embedded_pic_fnaddr_rtx == NULL) |
| { |
| rtx seq; |
| |
| embedded_pic_fnaddr_rtx = gen_reg_rtx (Pmode); |
| |
| /* Output code at function start to initialize the pseudo-reg. */ |
| /* ??? We used to do this in FINALIZE_PIC, but that does not work for |
| inline functions, because it is called after RTL for the function |
| has been copied. The pseudo-reg in embedded_pic_fnaddr_rtx however |
| does not get copied, and ends up not matching the rest of the RTL. |
| This solution works, but means that we get unnecessary code to |
| initialize this value every time a function is inlined into another |
| function. */ |
| start_sequence (); |
| emit_insn (gen_get_fnaddr (embedded_pic_fnaddr_rtx, |
| XEXP (DECL_RTL (current_function_decl), 0))); |
| seq = gen_sequence (); |
| end_sequence (); |
| push_topmost_sequence (); |
| emit_insn_after (seq, get_insns ()); |
| pop_topmost_sequence (); |
| } |
| |
| return gen_rtx (CONST, Pmode, |
| gen_rtx (MINUS, Pmode, x, |
| XEXP (DECL_RTL (current_function_decl), 0))); |
| } |
| |
| /* Return the appropriate instructions to move one operand to another. */ |
| |
| char * |
| mips_move_1word (operands, insn, unsignedp) |
| rtx operands[]; |
| rtx insn; |
| int unsignedp; |
| { |
| char *ret = 0; |
| rtx op0 = operands[0]; |
| rtx op1 = operands[1]; |
| enum rtx_code code0 = GET_CODE (op0); |
| enum rtx_code code1 = GET_CODE (op1); |
| enum machine_mode mode = GET_MODE (op0); |
| int subreg_word0 = 0; |
| int subreg_word1 = 0; |
| enum delay_type delay = DELAY_NONE; |
| |
| while (code0 == SUBREG) |
| { |
| subreg_word0 += SUBREG_WORD (op0); |
| op0 = SUBREG_REG (op0); |
| code0 = GET_CODE (op0); |
| } |
| |
| while (code1 == SUBREG) |
| { |
| subreg_word1 += SUBREG_WORD (op1); |
| op1 = SUBREG_REG (op1); |
| code1 = GET_CODE (op1); |
| } |
| |
| /* For our purposes, a condition code mode is the same as SImode. */ |
| if (mode == CCmode) |
| mode = SImode; |
| |
| if (code0 == REG) |
| { |
| int regno0 = REGNO (op0) + subreg_word0; |
| |
| if (code1 == REG) |
| { |
| int regno1 = REGNO (op1) + subreg_word1; |
| |
| /* Just in case, don't do anything for assigning a register |
| to itself, unless we are filling a delay slot. */ |
| if (regno0 == regno1 && set_nomacro == 0) |
| ret = ""; |
| |
| else if (GP_REG_P (regno0)) |
| { |
| if (GP_REG_P (regno1)) |
| ret = "move\t%0,%1"; |
| |
| else if (MD_REG_P (regno1)) |
| { |
| delay = DELAY_HILO; |
| if (regno1 != HILO_REGNUM) |
| ret = "mf%1\t%0"; |
| else |
| ret = "mflo\t%0"; |
| } |
| |
| else if (ST_REG_P (regno1) && mips_isa >= 4) |
| ret = "li\t%0,1\n\tmovf\t%0,%.,%1"; |
| |
| else |
| { |
| delay = DELAY_LOAD; |
| if (FP_REG_P (regno1)) |
| ret = "mfc1\t%0,%1"; |
| |
| else if (regno1 == FPSW_REGNUM && mips_isa < 4) |
| ret = "cfc1\t%0,$31"; |
| } |
| } |
| |
| else if (FP_REG_P (regno0)) |
| { |
| if (GP_REG_P (regno1)) |
| { |
| delay = DELAY_LOAD; |
| ret = "mtc1\t%1,%0"; |
| } |
| |
| if (FP_REG_P (regno1)) |
| ret = "mov.s\t%0,%1"; |
| } |
| |
| else if (MD_REG_P (regno0)) |
| { |
| if (GP_REG_P (regno1)) |
| { |
| delay = DELAY_HILO; |
| if (regno0 != HILO_REGNUM) |
| ret = "mt%0\t%1"; |
| } |
| } |
| |
| else if (regno0 == FPSW_REGNUM && mips_isa < 4) |
| { |
| if (GP_REG_P (regno1)) |
| { |
| delay = DELAY_LOAD; |
| ret = "ctc1\t%0,$31"; |
| } |
| } |
| } |
| |
| else if (code1 == MEM) |
| { |
| delay = DELAY_LOAD; |
| |
| if (TARGET_STATS) |
| mips_count_memory_refs (op1, 1); |
| |
| if (GP_REG_P (regno0)) |
| { |
| /* For loads, use the mode of the memory item, instead of the |
| target, so zero/sign extend can use this code as well. */ |
| switch (GET_MODE (op1)) |
| { |
| default: |
| break; |
| case SFmode: |
| ret = "lw\t%0,%1"; |
| break; |
| case SImode: |
| case CCmode: |
| ret = ((unsignedp && TARGET_64BIT) |
| ? "lwu\t%0,%1" |
| : "lw\t%0,%1"); |
| break; |
| case HImode: |
| ret = (unsignedp) ? "lhu\t%0,%1" : "lh\t%0,%1"; |
| break; |
| case QImode: |
| ret = (unsignedp) ? "lbu\t%0,%1" : "lb\t%0,%1"; |
| break; |
| } |
| } |
| |
| else if (FP_REG_P (regno0) && (mode == SImode || mode == SFmode)) |
| ret = "l.s\t%0,%1"; |
| |
| if (ret != (char *)0 && MEM_VOLATILE_P (op1)) |
| { |
| int i = strlen (ret); |
| if (i > sizeof (volatile_buffer) - sizeof ("%{%}")) |
| abort (); |
| |
| sprintf (volatile_buffer, "%%{%s%%}", ret); |
| ret = volatile_buffer; |
| } |
| } |
| |
| else if (code1 == CONST_INT |
| || (code1 == CONST_DOUBLE |
| && GET_MODE (op1) == VOIDmode)) |
| { |
| if (code1 == CONST_DOUBLE) |
| { |
| /* This can happen when storing constants into long long |
| bitfields. Just store the least significant word of |
| the value. */ |
| operands[1] = op1 = GEN_INT (CONST_DOUBLE_LOW (op1)); |
| } |
| |
| if (INTVAL (op1) == 0) |
| { |
| if (GP_REG_P (regno0)) |
| ret = "move\t%0,%z1"; |
| |
| else if (FP_REG_P (regno0)) |
| { |
| delay = DELAY_LOAD; |
| ret = "mtc1\t%z1,%0"; |
| } |
| |
| else if (MD_REG_P (regno0)) |
| { |
| delay = DELAY_HILO; |
| ret = "mt%0\t%."; |
| } |
| } |
| |
| else if (GP_REG_P (regno0)) |
| /* Don't use X format, because that will give out of range |
| numbers for 64 bit host and 32 bit target. */ |
| ret = "li\t%0,%1\t\t\t# %X1"; |
| } |
| |
| else if (code1 == CONST_DOUBLE && mode == SFmode) |
| { |
| if (op1 == CONST0_RTX (SFmode)) |
| { |
| if (GP_REG_P (regno0)) |
| ret = "move\t%0,%."; |
| |
| else if (FP_REG_P (regno0)) |
| { |
| delay = DELAY_LOAD; |
| ret = "mtc1\t%.,%0"; |
| } |
| } |
| |
| else |
| { |
| delay = DELAY_LOAD; |
| ret = "li.s\t%0,%1"; |
| } |
| } |
| |
| else if (code1 == LABEL_REF) |
| { |
| if (TARGET_STATS) |
| mips_count_memory_refs (op1, 1); |
| |
| ret = "la\t%0,%a1"; |
| } |
| |
| else if (code1 == SYMBOL_REF || code1 == CONST) |
| { |
| if (HALF_PIC_P () && CONSTANT_P (op1) && HALF_PIC_ADDRESS_P (op1)) |
| { |
| rtx offset = const0_rtx; |
| |
| if (GET_CODE (op1) == CONST) |
| op1 = eliminate_constant_term (XEXP (op1, 0), &offset); |
| |
| if (GET_CODE (op1) == SYMBOL_REF) |
| { |
| operands[2] = HALF_PIC_PTR (op1); |
| |
| if (TARGET_STATS) |
| mips_count_memory_refs (operands[2], 1); |
| |
| if (INTVAL (offset) == 0) |
| { |
| delay = DELAY_LOAD; |
| ret = (unsignedp && TARGET_64BIT |
| ? "lwu\t%0,%2" |
| : "lw\t%0,%2"); |
| } |
| else |
| { |
| dslots_load_total++; |
| operands[3] = offset; |
| if (unsignedp && TARGET_64BIT) |
| ret = (SMALL_INT (offset)) |
| ? "lwu\t%0,%2%#\n\tadd\t%0,%0,%3" |
| : "lwu\t%0,%2%#\n\t%[li\t%@,%3\n\tadd\t%0,%0,%@%]"; |
| else |
| ret = (SMALL_INT (offset)) |
| ? "lw\t%0,%2%#\n\tadd\t%0,%0,%3" |
| : "lw\t%0,%2%#\n\t%[li\t%@,%3\n\tadd\t%0,%0,%@%]"; |
| } |
| } |
| } |
| else |
| { |
| if (TARGET_STATS) |
| mips_count_memory_refs (op1, 1); |
| |
| ret = "la\t%0,%a1"; |
| } |
| } |
| |
| else if (code1 == PLUS) |
| { |
| rtx add_op0 = XEXP (op1, 0); |
| rtx add_op1 = XEXP (op1, 1); |
| |
| if (GET_CODE (XEXP (op1, 1)) == REG && GET_CODE (XEXP (op1, 0)) == CONST_INT) |
| { |
| add_op0 = XEXP (op1, 1); /* reverse operands */ |
| add_op1 = XEXP (op1, 0); |
| } |
| |
| operands[2] = add_op0; |
| operands[3] = add_op1; |
| ret = "add%:\t%0,%2,%3"; |
| } |
| |
| else if (code1 == HIGH) |
| { |
| operands[1] = XEXP (op1, 0); |
| ret = "lui\t%0,%%hi(%1)"; |
| } |
| } |
| |
| else if (code0 == MEM) |
| { |
| if (TARGET_STATS) |
| mips_count_memory_refs (op0, 1); |
| |
| if (code1 == REG) |
| { |
| int regno1 = REGNO (op1) + subreg_word1; |
| |
| if (GP_REG_P (regno1)) |
| { |
| switch (mode) |
| { |
| default: break; |
| case SFmode: ret = "sw\t%1,%0"; break; |
| case SImode: ret = "sw\t%1,%0"; break; |
| case HImode: ret = "sh\t%1,%0"; break; |
| case QImode: ret = "sb\t%1,%0"; break; |
| } |
| } |
| |
| else if (FP_REG_P (regno1) && (mode == SImode || mode == SFmode)) |
| ret = "s.s\t%1,%0"; |
| } |
| |
| else if (code1 == CONST_INT && INTVAL (op1) == 0) |
| { |
| switch (mode) |
| { |
| default: break; |
| case SFmode: ret = "sw\t%z1,%0"; break; |
| case SImode: ret = "sw\t%z1,%0"; break; |
| case HImode: ret = "sh\t%z1,%0"; break; |
| case QImode: ret = "sb\t%z1,%0"; break; |
| } |
| } |
| |
| else if (code1 == CONST_DOUBLE && op1 == CONST0_RTX (mode)) |
| { |
| switch (mode) |
| { |
| default: break; |
| case SFmode: ret = "sw\t%.,%0"; break; |
| case SImode: ret = "sw\t%.,%0"; break; |
| case HImode: ret = "sh\t%.,%0"; break; |
| case QImode: ret = "sb\t%.,%0"; break; |
| } |
| } |
| |
| if (ret != (char *)0 && MEM_VOLATILE_P (op0)) |
| { |
| int i = strlen (ret); |
| if (i > sizeof (volatile_buffer) - sizeof ("%{%}")) |
| abort (); |
| |
| sprintf (volatile_buffer, "%%{%s%%}", ret); |
| ret = volatile_buffer; |
| } |
| } |
| |
| if (ret == (char *)0) |
| { |
| abort_with_insn (insn, "Bad move"); |
| return 0; |
| } |
| |
| if (delay != DELAY_NONE) |
| return mips_fill_delay_slot (ret, delay, operands, insn); |
| |
| return ret; |
| } |
| |
| |
| /* Return the appropriate instructions to move 2 words */ |
| |
| char * |
| mips_move_2words (operands, insn) |
| rtx operands[]; |
| rtx insn; |
| { |
| char *ret = 0; |
| rtx op0 = operands[0]; |
| rtx op1 = operands[1]; |
| enum rtx_code code0 = GET_CODE (operands[0]); |
| enum rtx_code code1 = GET_CODE (operands[1]); |
| int subreg_word0 = 0; |
| int subreg_word1 = 0; |
| enum delay_type delay = DELAY_NONE; |
| |
| while (code0 == SUBREG) |
| { |
| subreg_word0 += SUBREG_WORD (op0); |
| op0 = SUBREG_REG (op0); |
| code0 = GET_CODE (op0); |
| } |
| |
| if (code1 == SIGN_EXTEND) |
| { |
| op1 = XEXP (op1, 0); |
| code1 = GET_CODE (op1); |
| } |
| |
| while (code1 == SUBREG) |
| { |
| subreg_word1 += SUBREG_WORD (op1); |
| op1 = SUBREG_REG (op1); |
| code1 = GET_CODE (op1); |
| } |
| |
| /* Sanity check. */ |
| if (GET_CODE (operands[1]) == SIGN_EXTEND |
| && code1 != REG |
| && code1 != CONST_INT |
| /* The following three can happen as the result of a questionable |
| cast. */ |
| && code1 != LABEL_REF |
| && code1 != SYMBOL_REF |
| && code1 != CONST) |
| abort (); |
| |
| if (code0 == REG) |
| { |
| int regno0 = REGNO (op0) + subreg_word0; |
| |
| if (code1 == REG) |
| { |
| int regno1 = REGNO (op1) + subreg_word1; |
| |
| /* Just in case, don't do anything for assigning a register |
| to itself, unless we are filling a delay slot. */ |
| if (regno0 == regno1 && set_nomacro == 0) |
| ret = ""; |
| |
| else if (FP_REG_P (regno0)) |
| { |
| if (FP_REG_P (regno1)) |
| ret = "mov.d\t%0,%1"; |
| |
| else |
| { |
| delay = DELAY_LOAD; |
| if (TARGET_FLOAT64) |
| { |
| if (!TARGET_64BIT) |
| abort_with_insn (insn, "Bad move"); |
| #ifdef TARGET_FP_CALL_32 |
| if (FP_CALL_GP_REG_P (regno1)) |
| ret = "dsll\t%1,32\n\tor\t%1,%D1\n\tdmtc1\t%1,%0"; |
| else |
| #endif |
| ret = "dmtc1\t%1,%0"; |
| } |
| else |
| ret = "mtc1\t%L1,%0\n\tmtc1\t%M1,%D0"; |
| } |
| } |
| |
| else if (FP_REG_P (regno1)) |
| { |
| delay = DELAY_LOAD; |
| if (TARGET_FLOAT64) |
| { |
| if (!TARGET_64BIT) |
| abort_with_insn (insn, "Bad move"); |
| #ifdef TARGET_FP_CALL_32 |
| if (FP_CALL_GP_REG_P (regno0)) |
| ret = "dmfc1\t%0,%1\n\tmfc1\t%D0,%1\n\tdsrl\t%0,32"; |
| else |
| #endif |
| ret = "dmfc1\t%0,%1"; |
| } |
| else |
| ret = "mfc1\t%L0,%1\n\tmfc1\t%M0,%D1"; |
| } |
| |
| else if (MD_REG_P (regno0) && GP_REG_P (regno1)) |
| { |
| delay = DELAY_HILO; |
| if (TARGET_64BIT) |
| { |
| if (regno0 != HILO_REGNUM) |
| ret = "mt%0\t%1"; |
| else if (regno1 == 0) |
| ret = "mtlo\t%.\n\tmthi\t%."; |
| } |
| else |
| ret = "mthi\t%M1\n\tmtlo\t%L1"; |
| } |
| |
| else if (GP_REG_P (regno0) && MD_REG_P (regno1)) |
| { |
| delay = DELAY_HILO; |
| if (TARGET_64BIT) |
| { |
| if (regno1 != HILO_REGNUM) |
| ret = "mf%1\t%0"; |
| } |
| else |
| ret = "mfhi\t%M0\n\tmflo\t%L0"; |
| } |
| |
| else if (TARGET_64BIT) |
| ret = "move\t%0,%1"; |
| |
| else if (regno0 != (regno1+1)) |
| ret = "move\t%0,%1\n\tmove\t%D0,%D1"; |
| |
| else |
| ret = "move\t%D0,%D1\n\tmove\t%0,%1"; |
| } |
| |
| else if (code1 == CONST_DOUBLE) |
| { |
| /* Move zero from $0 unless !TARGET_64BIT and recipient |
| is 64-bit fp reg, in which case generate a constant. */ |
| if (op1 != CONST0_RTX (GET_MODE (op1)) |
| || (TARGET_FLOAT64 && !TARGET_64BIT && FP_REG_P (regno0))) |
| { |
| if (GET_MODE (op1) == DFmode) |
| { |
| delay = DELAY_LOAD; |
| #ifdef TARGET_FP_CALL_32 |
| if (FP_CALL_GP_REG_P (regno0)) |
| { |
| if (TARGET_FLOAT64 && !TARGET_64BIT) |
| { |
| split_double (op1, operands + 2, operands + 3); |
| ret = "li\t%0,%2\n\tli\t%D0,%3"; |
| } |
| else |
| ret = "li.d\t%0,%1\n\tdsll\t%D0,%0,32\n\tdsrl\t%D0,32\n\tdsrl\t%0,32"; |
| } |
| else |
| #endif |
| ret = "li.d\t%0,%1"; |
| } |
| |
| else if (TARGET_64BIT) |
| ret = "dli\t%0,%1"; |
| |
| else |
| { |
| split_double (op1, operands + 2, operands + 3); |
| ret = "li\t%0,%2\n\tli\t%D0,%3"; |
| } |
| } |
| |
| else |
| { |
| if (GP_REG_P (regno0)) |
| ret = (TARGET_64BIT |
| #ifdef TARGET_FP_CALL_32 |
| && ! FP_CALL_GP_REG_P (regno0) |
| #endif |
| ) |
| ? "move\t%0,%." |
| : "move\t%0,%.\n\tmove\t%D0,%."; |
| |
| else if (FP_REG_P (regno0)) |
| { |
| delay = DELAY_LOAD; |
| ret = (TARGET_64BIT) |
| ? "dmtc1\t%.,%0" |
| : "mtc1\t%.,%0\n\tmtc1\t%.,%D0"; |
| } |
| } |
| } |
| |
| else if (code1 == CONST_INT && INTVAL (op1) == 0) |
| { |
| if (GP_REG_P (regno0)) |
| ret = (TARGET_64BIT) |
| ? "move\t%0,%." |
| : "move\t%0,%.\n\tmove\t%D0,%."; |
| |
| else if (FP_REG_P (regno0)) |
| { |
| delay = DELAY_LOAD; |
| ret = (TARGET_64BIT) |
| ? "dmtc1\t%.,%0" |
| : (TARGET_FLOAT64 |
| ? "li.d\t%0,%1" |
| : "mtc1\t%.,%0\n\tmtc1\t%.,%D0"); |
| } |
| else if (MD_REG_P (regno0)) |
| { |
| delay = DELAY_HILO; |
| if (regno0 != HILO_REGNUM) |
| ret = "mt%0\t%.\n"; |
| else |
| ret = "mtlo\t%.\n\tmthi\t%."; |
| } |
| } |
| |
| else if (code1 == CONST_INT && GET_MODE (op0) == DImode && GP_REG_P (regno0)) |
| { |
| if (TARGET_64BIT) |
| { |
| if (GET_CODE (operands[1]) == SIGN_EXTEND) |
| ret = "li\t%0,%1\t\t# %X1"; |
| else if (HOST_BITS_PER_WIDE_INT < 64) |
| /* We can't use 'X' for negative numbers, because then we won't |
| get the right value for the upper 32 bits. */ |
| ret = ((INTVAL (op1) < 0) ? "dli\t%0,%1\t\t\t# %X1" |
| : "dli\t%0,%X1\t\t# %1"); |
| else |
| /* We must use 'X', because otherwise LONG_MIN will print as |
| a number that the assembler won't accept. */ |
| ret = "dli\t%0,%X1\t\t# %1"; |
| } |
| else if (HOST_BITS_PER_WIDE_INT < 64) |
| { |
| operands[2] = GEN_INT (INTVAL (operands[1]) >= 0 ? 0 : -1); |
| ret = "li\t%M0,%2\n\tli\t%L0,%1"; |
| } |
| else |
| { |
| /* We use multiple shifts here, to avoid warnings about out |
| of range shifts on 32 bit hosts. */ |
| operands[2] = GEN_INT (INTVAL (operands[1]) >> 16 >> 16); |
| operands[1] = GEN_INT (INTVAL (operands[1]) << 16 << 16 >> 16 >> 16); |
| ret = "li\t%M0,%2\n\tli\t%L0,%1"; |
| } |
| } |
| |
| else if (code1 == MEM) |
| { |
| delay = DELAY_LOAD; |
| |
| if (TARGET_STATS) |
| mips_count_memory_refs (op1, 2); |
| |
| if (FP_REG_P (regno0)) |
| ret = "l.d\t%0,%1"; |
| |
| else if (TARGET_64BIT) |
| { |
| #ifdef TARGET_FP_CALL_32 |
| if (FP_CALL_GP_REG_P (regno0)) |
| { |
| if (offsettable_address_p (FALSE, SImode, op1)) |
| ret = "lwu\t%0,%1\n\tlwu\t%D0,4+%1"; |
| else |
| ret = "ld\t%0,%1\n\tdsll\t%D0,%0,32\n\tdsrl\t%D0,32\n\tdsrl\t%0,32"; |
| } |
| else |
| #endif |
| ret = "ld\t%0,%1"; |
| } |
| |
| else if (offsettable_address_p (1, DFmode, XEXP (op1, 0))) |
| { |
| operands[2] = adj_offsettable_operand (op1, 4); |
| if (reg_mentioned_p (op0, op1)) |
| ret = "lw\t%D0,%2\n\tlw\t%0,%1"; |
| else |
| ret = "lw\t%0,%1\n\tlw\t%D0,%2"; |
| } |
| |
| if (ret != (char *)0 && MEM_VOLATILE_P (op1)) |
| { |
| int i = strlen (ret); |
| if (i > sizeof (volatile_buffer) - sizeof ("%{%}")) |
| abort (); |
| |
| sprintf (volatile_buffer, "%%{%s%%}", ret); |
| ret = volatile_buffer; |
| } |
| } |
| |
| else if (code1 == LABEL_REF |
| || code1 == SYMBOL_REF |
| || code1 == CONST) |
| { |
| if (TARGET_STATS) |
| mips_count_memory_refs (op1, 2); |
| |
| if (GET_CODE (operands[1]) == SIGN_EXTEND) |
| /* We deliberately remove the 'a' from '%1', so that we don't |
| have to add SIGN_EXTEND support to print_operand_address. |
| print_operand will just call print_operand_address in this |
| case, so there is no problem. */ |
| ret = "la\t%0,%1"; |
| else |
| ret = "dla\t%0,%a1"; |
| } |
| } |
| |
| else if (code0 == MEM) |
| { |
| if (code1 == REG) |
| { |
| int regno1 = REGNO (op1) + subreg_word1; |
| |
| if (FP_REG_P (regno1)) |
| ret = "s.d\t%1,%0"; |
| |
| else if (TARGET_64BIT) |
| { |
| #ifdef TARGET_FP_CALL_32 |
| if (FP_CALL_GP_REG_P (regno1)) |
| ret = "dsll\t%1,32\n\tor\t%1,%D1\n\tsd\t%1,%0"; |
| else |
| #endif |
| ret = "sd\t%1,%0"; |
| } |
| |
| else if (offsettable_address_p (1, DFmode, XEXP (op0, 0))) |
| { |
| operands[2] = adj_offsettable_operand (op0, 4); |
| ret = "sw\t%1,%0\n\tsw\t%D1,%2"; |
| } |
| } |
| |
| else if (((code1 == CONST_INT && INTVAL (op1) == 0) |
| || (code1 == CONST_DOUBLE |
| && op1 == CONST0_RTX (GET_MODE (op1)))) |
| && (TARGET_64BIT |
| || offsettable_address_p (1, DFmode, XEXP (op0, 0)))) |
| { |
| if (TARGET_64BIT) |
| ret = "sd\t%.,%0"; |
| else |
| { |
| operands[2] = adj_offsettable_operand (op0, 4); |
| ret = "sw\t%.,%0\n\tsw\t%.,%2"; |
| } |
| } |
| |
| if (TARGET_STATS) |
| mips_count_memory_refs (op0, 2); |
| |
| if (ret != (char *)0 && MEM_VOLATILE_P (op0)) |
| { |
| int i = strlen (ret); |
| if (i > sizeof (volatile_buffer) - sizeof ("%{%}")) |
| abort (); |
| |
| sprintf (volatile_buffer, "%%{%s%%}", ret); |
| ret = volatile_buffer; |
| } |
| } |
| |
| if (ret == (char *)0) |
| { |
| abort_with_insn (insn, "Bad move"); |
| return 0; |
| } |
| |
| if (delay != DELAY_NONE) |
| return mips_fill_delay_slot (ret, delay, operands, insn); |
| |
| return ret; |
| } |
| |
| |
| /* Provide the costs of an addressing mode that contains ADDR. |
| If ADDR is not a valid address, its cost is irrelevant. */ |
| |
| int |
| mips_address_cost (addr) |
| rtx addr; |
| { |
| switch (GET_CODE (addr)) |
| { |
| default: |
| break; |
| |
| case LO_SUM: |
| return 1; |
| |
| case LABEL_REF: |
| return 2; |
| |
| case CONST: |
| { |
| rtx offset = const0_rtx; |
| addr = eliminate_constant_term (XEXP (addr, 0), &offset); |
| if (GET_CODE (addr) == LABEL_REF) |
| return 2; |
| |
| if (GET_CODE (addr) != SYMBOL_REF) |
| return 4; |
| |
| if (! SMALL_INT (offset)) |
| return 2; |
| } |
| /* fall through */ |
| |
| case SYMBOL_REF: |
| return SYMBOL_REF_FLAG (addr) ? 1 : 2; |
| |
| case PLUS: |
| { |
| register rtx plus0 = XEXP (addr, 0); |
| register rtx plus1 = XEXP (addr, 1); |
| |
| if (GET_CODE (plus0) != REG && GET_CODE (plus1) == REG) |
| { |
| plus0 = XEXP (addr, 1); |
| plus1 = XEXP (addr, 0); |
| } |
| |
| if (GET_CODE (plus0) != REG) |
| break; |
| |
| switch (GET_CODE (plus1)) |
| { |
| default: |
| break; |
| |
| case CONST_INT: |
| return (SMALL_INT (plus1) ? 1 : 2); |
| |
| case CONST: |
| case SYMBOL_REF: |
| case LABEL_REF: |
| case HIGH: |
| case LO_SUM: |
| return mips_address_cost (plus1) + 1; |
| } |
| } |
| } |
| |
| return 4; |
| } |
| |
| /* Return true if X is an address which needs a temporary register when |
| reloaded while generating PIC code. */ |
| |
| int |
| pic_address_needs_scratch (x) |
| rtx x; |
| { |
| /* An address which is a symbolic plus a non SMALL_INT needs a temp reg. */ |
| if (GET_CODE (x) == CONST && GET_CODE (XEXP (x, 0)) == PLUS |
| && GET_CODE (XEXP (XEXP (x, 0), 0)) == SYMBOL_REF |
| && GET_CODE (XEXP (XEXP (x, 0), 1)) == CONST_INT |
| && ! SMALL_INT (XEXP (XEXP (x, 0), 1))) |
| return 1; |
| |
| return 0; |
| } |
| |
| /* Make normal rtx_code into something we can index from an array */ |
| |
| static enum internal_test |
| map_test_to_internal_test (test_code) |
| enum rtx_code test_code; |
| { |
| enum internal_test test = ITEST_MAX; |
| |
| switch (test_code) |
| { |
| default: break; |
| case EQ: test = ITEST_EQ; break; |
| case NE: test = ITEST_NE; break; |
| case GT: test = ITEST_GT; break; |
| case GE: test = ITEST_GE; break; |
| case LT: test = ITEST_LT; break; |
| case LE: test = ITEST_LE; break; |
| case GTU: test = ITEST_GTU; break; |
| case GEU: test = ITEST_GEU; break; |
| case LTU: test = ITEST_LTU; break; |
| case LEU: test = ITEST_LEU; break; |
| } |
| |
| return test; |
| } |
| |
| |
| /* Generate the code to compare two integer values. The return value is: |
| (reg:SI xx) The pseudo register the comparison is in |
| (rtx)0 No register, generate a simple branch. |
| |
| ??? This is called with result nonzero by the Scond patterns in |
| mips.md. These patterns are called with a target in the mode of |
| the Scond instruction pattern. Since this must be a constant, we |
| must use SImode. This means that if RESULT is non-zero, it will |
| always be an SImode register, even if TARGET_64BIT is true. We |
| cope with this by calling convert_move rather than emit_move_insn. |
| This will sometimes lead to an unnecessary extension of the result; |
| for example: |
| |
| long long |
| foo (long long i) |
| { |
| return i < 5; |
| } |
| |
| */ |
| |
| rtx |
| gen_int_relational (test_code, result, cmp0, cmp1, p_invert) |
| enum rtx_code test_code; /* relational test (EQ, etc) */ |
| rtx result; /* result to store comp. or 0 if branch */ |
| rtx cmp0; /* first operand to compare */ |
| rtx cmp1; /* second operand to compare */ |
| int *p_invert; /* NULL or ptr to hold whether branch needs */ |
| /* to reverse its test */ |
| { |
| struct cmp_info { |
| enum rtx_code test_code; /* code to use in instruction (LT vs. LTU) */ |
| int const_low; /* low bound of constant we can accept */ |
| int const_high; /* high bound of constant we can accept */ |
| int const_add; /* constant to add (convert LE -> LT) */ |
| int reverse_regs; /* reverse registers in test */ |
| int invert_const; /* != 0 if invert value if cmp1 is constant */ |
| int invert_reg; /* != 0 if invert value if cmp1 is register */ |
| int unsignedp; /* != 0 for unsigned comparisons. */ |
| }; |
| |
| static struct cmp_info info[ (int)ITEST_MAX ] = { |
| |
| { XOR, 0, 65535, 0, 0, 0, 0, 0 }, /* EQ */ |
| { XOR, 0, 65535, 0, 0, 1, 1, 0 }, /* NE */ |
| { LT, -32769, 32766, 1, 1, 1, 0, 0 }, /* GT */ |
| { LT, -32768, 32767, 0, 0, 1, 1, 0 }, /* GE */ |
| { LT, -32768, 32767, 0, 0, 0, 0, 0 }, /* LT */ |
| { LT, -32769, 32766, 1, 1, 0, 1, 0 }, /* LE */ |
| { LTU, -32769, 32766, 1, 1, 1, 0, 1 }, /* GTU */ |
| { LTU, -32768, 32767, 0, 0, 1, 1, 1 }, /* GEU */ |
| { LTU, -32768, 32767, 0, 0, 0, 0, 1 }, /* LTU */ |
| { LTU, -32769, 32766, 1, 1, 0, 1, 1 }, /* LEU */ |
| }; |
| |
| enum internal_test test; |
| enum machine_mode mode; |
| struct cmp_info *p_info; |
| int branch_p; |
| int eqne_p; |
| int invert; |
| rtx reg; |
| rtx reg2; |
| |
| test = map_test_to_internal_test (test_code); |
| if (test == ITEST_MAX) |
| abort (); |
| |
| p_info = &info[ (int)test ]; |
| eqne_p = (p_info->test_code == XOR); |
| |
| mode = GET_MODE (cmp0); |
| if (mode == VOIDmode) |
| mode = GET_MODE (cmp1); |
| |
| /* Eliminate simple branches */ |
| branch_p = (result == (rtx)0); |
| if (branch_p) |
| { |
| if (GET_CODE (cmp0) == REG || GET_CODE (cmp0) == SUBREG) |
| { |
| /* Comparisons against zero are simple branches */ |
| if (GET_CODE (cmp1) == CONST_INT && INTVAL (cmp1) == 0) |
| return (rtx)0; |
| |
| /* Test for beq/bne. */ |
| if (eqne_p) |
| return (rtx)0; |
| } |
| |
| /* allocate a pseudo to calculate the value in. */ |
| result = gen_reg_rtx (mode); |
| } |
| |
| /* Make sure we can handle any constants given to us. */ |
| if (GET_CODE (cmp0) == CONST_INT) |
| cmp0 = force_reg (mode, cmp0); |
| |
| if (GET_CODE (cmp1) == CONST_INT) |
| { |
| HOST_WIDE_INT value = INTVAL (cmp1); |
| if (value < p_info->const_low |
| || value > p_info->const_high |
| /* ??? Why? And why wasn't the similar code below modified too? */ |
| || (TARGET_64BIT |
| && HOST_BITS_PER_WIDE_INT < 64 |
| && p_info->const_add != 0 |
| && ((p_info->unsignedp |
| ? ((unsigned HOST_WIDE_INT) (value + p_info->const_add) |
| > INTVAL (cmp1)) |
| : (value + p_info->const_add) > INTVAL (cmp1)) |
| != (p_info->const_add > 0)))) |
| cmp1 = force_reg (mode, cmp1); |
| } |
| |
| /* See if we need to invert the result. */ |
| invert = (GET_CODE (cmp1) == CONST_INT) |
| ? p_info->invert_const |
| : p_info->invert_reg; |
| |
| if (p_invert != (int *)0) |
| { |
| *p_invert = invert; |
| invert = FALSE; |
| } |
| |
| /* Comparison to constants, may involve adding 1 to change a LT into LE. |
| Comparison between two registers, may involve switching operands. */ |
| if (GET_CODE (cmp1) == CONST_INT) |
| { |
| if (p_info->const_add != 0) |
| { |
| HOST_WIDE_INT new = INTVAL (cmp1) + p_info->const_add; |
| /* If modification of cmp1 caused overflow, |
| we would get the wrong answer if we follow the usual path; |
| thus, x > 0xffffffffU would turn into x > 0U. */ |
| if ((p_info->unsignedp |
| ? (unsigned HOST_WIDE_INT) new > INTVAL (cmp1) |
| : new > INTVAL (cmp1)) |
| != (p_info->const_add > 0)) |
| { |
| /* This test is always true, but if INVERT is true then |
| the result of the test needs to be inverted so 0 should |
| be returned instead. */ |
| emit_move_insn (result, invert ? const0_rtx : const_true_rtx); |
| return result; |
| } |
| else |
| cmp1 = GEN_INT (new); |
| } |
| } |
| else if (p_info->reverse_regs) |
| { |
| rtx temp = cmp0; |
| cmp0 = cmp1; |
| cmp1 = temp; |
| } |
| |
| if (test == ITEST_NE && GET_CODE (cmp1) == CONST_INT && INTVAL (cmp1) == 0) |
| reg = cmp0; |
| else |
| { |
| reg = (invert || eqne_p) ? gen_reg_rtx (mode) : result; |
| convert_move (reg, gen_rtx (p_info->test_code, mode, cmp0, cmp1), 0); |
| } |
| |
| if (test == ITEST_NE) |
| { |
| convert_move (result, gen_rtx (GTU, mode, reg, const0_rtx), 0); |
| invert = FALSE; |
| } |
| |
| else if (test == ITEST_EQ) |
| { |
| reg2 = (invert) ? gen_reg_rtx (mode) : result; |
| convert_move (reg2, gen_rtx (LTU, mode, reg, const1_rtx), 0); |
| reg = reg2; |
| } |
| |
| if (invert) |
| convert_move (result, gen_rtx (XOR, mode, reg, const1_rtx), 0); |
| |
| return result; |
| } |
| |
| |
| /* Emit the common code for doing conditional branches. |
| operand[0] is the label to jump to. |
| The comparison operands are saved away by cmp{si,di,sf,df}. */ |
| |
| void |
| gen_conditional_branch (operands, test_code) |
| rtx operands[]; |
| enum rtx_code test_code; |
| { |
| enum cmp_type type = branch_type; |
| rtx cmp0 = branch_cmp[0]; |
| rtx cmp1 = branch_cmp[1]; |
| enum machine_mode mode; |
| rtx reg; |
| int invert; |
| rtx label1, label2; |
| |
| switch (type) |
| { |
| default: |
| abort_with_insn (gen_rtx (test_code, VOIDmode, cmp0, cmp1), "bad test"); |
| |
| case CMP_SI: |
| case CMP_DI: |
| mode = type == CMP_SI ? SImode : DImode; |
| invert = FALSE; |
| reg = gen_int_relational (test_code, NULL_RTX, cmp0, cmp1, &invert); |
| if (reg) |
| { |
| cmp0 = reg; |
| cmp1 = const0_rtx; |
| test_code = NE; |
| } |
| else if (GET_CODE (cmp1) == CONST_INT && INTVAL (cmp1) != 0) |
| { |
| /* We don't want to build a comparison against a non-zero |
| constant. */ |
| cmp1 = force_reg (mode, cmp1); |
| } |
| break; |
| |
| case CMP_SF: |
| case CMP_DF: |
| if (mips_isa < 4) |
| reg = gen_rtx (REG, CCmode, FPSW_REGNUM); |
| else |
| reg = gen_reg_rtx (CCmode); |
| |
| /* For cmp0 != cmp1, build cmp0 == cmp1, and test for result == |
| 0 in the instruction built below. The MIPS FPU handles |
| inequality testing by testing for equality and looking for a |
| false result. */ |
| emit_insn (gen_rtx (SET, VOIDmode, |
| reg, |
| gen_rtx (test_code == NE ? EQ : test_code, |
| CCmode, cmp0, cmp1))); |
| |
| test_code = test_code == NE ? EQ : NE; |
| mode = CCmode; |
| cmp0 = reg; |
| cmp1 = const0_rtx; |
| invert = FALSE; |
| break; |
| } |
| |
| /* Generate the branch. */ |
| |
| label1 = gen_rtx (LABEL_REF, VOIDmode, operands[0]); |
| label2 = pc_rtx; |
| |
| if (invert) |
| { |
| label2 = label1; |
| label1 = pc_rtx; |
| } |
| |
| emit_jump_insn (gen_rtx (SET, VOIDmode, |
| pc_rtx, |
| gen_rtx (IF_THEN_ELSE, VOIDmode, |
| gen_rtx (test_code, mode, cmp0, cmp1), |
| label1, |
| label2))); |
| } |
| |
| /* Emit the common code for conditional moves. OPERANDS is the array |
| of operands passed to the conditional move defined_expand. */ |
| |
| void |
| gen_conditional_move (operands) |
| rtx *operands; |
| { |
| rtx op0 = branch_cmp[0]; |
| rtx op1 = branch_cmp[1]; |
| enum machine_mode mode = GET_MODE (branch_cmp[0]); |
| enum rtx_code cmp_code = GET_CODE (operands[1]); |
| enum rtx_code move_code = NE; |
| enum machine_mode op_mode = GET_MODE (operands[0]); |
| enum machine_mode cmp_mode; |
| rtx cmp_reg; |
| |
| if (GET_MODE_CLASS (mode) != MODE_FLOAT) |
| { |
| switch (cmp_code) |
| { |
| case EQ: |
| cmp_code = XOR; |
| move_code = EQ; |
| break; |
| case NE: |
| cmp_code = XOR; |
| break; |
| case LT: |
| break; |
| case GE: |
| cmp_code = LT; |
| move_code = EQ; |
| break; |
| case GT: |
| cmp_code = LT; |
| op0 = force_reg (mode, branch_cmp[1]); |
| op1 = branch_cmp[0]; |
| break; |
| case LE: |
| cmp_code = LT; |
| op0 = force_reg (mode, branch_cmp[1]); |
| op1 = branch_cmp[0]; |
| move_code = EQ; |
| break; |
| case LTU: |
| break; |
| case GEU: |
| cmp_code = LTU; |
| move_code = EQ; |
| break; |
| case GTU: |
| cmp_code = LTU; |
| op0 = force_reg (mode, branch_cmp[1]); |
| op1 = branch_cmp[0]; |
| break; |
| case LEU: |
| cmp_code = LTU; |
| op0 = force_reg (mode, branch_cmp[1]); |
| op1 = branch_cmp[0]; |
| move_code = EQ; |
| break; |
| default: |
| abort (); |
| } |
| } |
| else |
| { |
| if (cmp_code == NE) |
| { |
| cmp_code = EQ; |
| move_code = EQ; |
| } |
| } |
| |
| if (mode == SImode || mode == DImode) |
| cmp_mode = mode; |
| else if (mode == SFmode || mode == DFmode) |
| cmp_mode = CCmode; |
| else |
| abort (); |
| |
| cmp_reg = gen_reg_rtx (cmp_mode); |
| emit_insn (gen_rtx (SET, cmp_mode, |
| cmp_reg, |
| gen_rtx (cmp_code, cmp_mode, op0, op1))); |
| emit_insn (gen_rtx (SET, op_mode, |
| operands[0], |
| gen_rtx (IF_THEN_ELSE, op_mode, |
| gen_rtx (move_code, VOIDmode, |
| cmp_reg, |
| CONST0_RTX (SImode)), |
| operands[2], |
| operands[3]))); |
| } |
| |
| /* Write a loop to move a constant number of bytes. Generate load/stores as follows: |
| |
| do { |
| temp1 = src[0]; |
| temp2 = src[1]; |
| ... |
| temp<last> = src[MAX_MOVE_REGS-1]; |
| dest[0] = temp1; |
| dest[1] = temp2; |
| ... |
| dest[MAX_MOVE_REGS-1] = temp<last>; |
| src += MAX_MOVE_REGS; |
| dest += MAX_MOVE_REGS; |
| } while (src != final); |
| |
| This way, no NOP's are needed, and only MAX_MOVE_REGS+3 temp |
| registers are needed. |
| |
| Aligned moves move MAX_MOVE_REGS*4 bytes every (2*MAX_MOVE_REGS)+3 |
| cycles, unaligned moves move MAX_MOVE_REGS*4 bytes every |
| (4*MAX_MOVE_REGS)+3 cycles, assuming no cache misses. */ |
| |
| #define MAX_MOVE_REGS 4 |
| #define MAX_MOVE_BYTES (MAX_MOVE_REGS * UNITS_PER_WORD) |
| |
| static void |
| block_move_loop (dest_reg, src_reg, bytes, align, orig_dest, orig_src) |
| rtx dest_reg; /* register holding destination address */ |
| rtx src_reg; /* register holding source address */ |
| int bytes; /* # bytes to move */ |
| int align; /* alignment */ |
| rtx orig_dest; /* original dest for change_address */ |
| rtx orig_src; /* original source for making a reg note */ |
| { |
| rtx dest_mem = change_address (orig_dest, BLKmode, dest_reg); |
| rtx src_mem = change_address (orig_src, BLKmode, src_reg); |
| rtx align_rtx = GEN_INT (align); |
| rtx label; |
| rtx final_src; |
| rtx bytes_rtx; |
| int leftover; |
| |
| if (bytes < 2*MAX_MOVE_BYTES) |
| abort (); |
| |
| leftover = bytes % MAX_MOVE_BYTES; |
| bytes -= leftover; |
| |
| label = gen_label_rtx (); |
| final_src = gen_reg_rtx (Pmode); |
| bytes_rtx = GEN_INT (bytes); |
| |
| if (bytes > 0x7fff) |
| { |
| if (TARGET_LONG64) |
| { |
| emit_insn (gen_movdi (final_src, bytes_rtx)); |
| emit_insn (gen_adddi3 (final_src, final_src, src_reg)); |
| } |
| else |
| { |
| emit_insn (gen_movsi (final_src, bytes_rtx)); |
| emit_insn (gen_addsi3 (final_src, final_src, src_reg)); |
| } |
| } |
| else |
| { |
| if (TARGET_LONG64) |
| emit_insn (gen_adddi3 (final_src, src_reg, bytes_rtx)); |
| else |
| emit_insn (gen_addsi3 (final_src, src_reg, bytes_rtx)); |
| } |
| |
| emit_label (label); |
| |
| bytes_rtx = GEN_INT (MAX_MOVE_BYTES); |
| emit_insn (gen_movstrsi_internal (dest_mem, src_mem, bytes_rtx, align_rtx)); |
| if (TARGET_LONG64) |
| { |
| emit_insn (gen_adddi3 (src_reg, src_reg, bytes_rtx)); |
| emit_insn (gen_adddi3 (dest_reg, dest_reg, bytes_rtx)); |
| emit_insn (gen_cmpdi (src_reg, final_src)); |
| } |
| else |
| { |
| emit_insn (gen_addsi3 (src_reg, src_reg, bytes_rtx)); |
| emit_insn (gen_addsi3 (dest_reg, dest_reg, bytes_rtx)); |
| emit_insn (gen_cmpsi (src_reg, final_src)); |
| } |
| emit_jump_insn (gen_bne (label)); |
| |
| if (leftover) |
| emit_insn (gen_movstrsi_internal (dest_mem, src_mem, |
| GEN_INT (leftover), |
| align_rtx)); |
| } |
| |
| /* Use a library function to move some bytes. */ |
| |
| static void |
| block_move_call (dest_reg, src_reg, bytes_rtx) |
| rtx dest_reg; |
| rtx src_reg; |
| rtx bytes_rtx; |
| { |
| /* We want to pass the size as Pmode, which will normally be SImode |
| but will be DImode if we are using 64 bit longs and pointers. */ |
| if (GET_MODE (bytes_rtx) != VOIDmode |
| && GET_MODE (bytes_rtx) != Pmode) |
| bytes_rtx = convert_to_mode (Pmode, bytes_rtx, TRUE); |
| |
| #ifdef TARGET_MEM_FUNCTIONS |
| emit_library_call (gen_rtx (SYMBOL_REF, Pmode, "memcpy"), 0, |
| VOIDmode, 3, |
| dest_reg, Pmode, |
| src_reg, Pmode, |
| convert_to_mode (TYPE_MODE (sizetype), bytes_rtx, |
| TREE_UNSIGNED (sizetype)), |
| TYPE_MODE (sizetype)); |
| #else |
| emit_library_call (gen_rtx (SYMBOL_REF, Pmode, "bcopy"), 0, |
| VOIDmode, 3, |
| src_reg, Pmode, |
| dest_reg, Pmode, |
| convert_to_mode (TYPE_MODE (integer_type_node), |
| bytes_rtx, |
| TREE_UNSIGNED (integer_type_node)), |
| TYPE_MODE (integer_type_node)); |
| #endif |
| } |
| |
| |
| /* Expand string/block move operations. |
| |
| operands[0] is the pointer to the destination. |
| operands[1] is the pointer to the source. |
| operands[2] is the number of bytes to move. |
| operands[3] is the alignment. */ |
| |
| void |
| expand_block_move (operands) |
| rtx operands[]; |
| { |
| rtx bytes_rtx = operands[2]; |
| rtx align_rtx = operands[3]; |
| int constp = (GET_CODE (bytes_rtx) == CONST_INT); |
| int bytes = (constp ? INTVAL (bytes_rtx) : 0); |
| int align = INTVAL (align_rtx); |
| rtx orig_src = operands[1]; |
| rtx orig_dest = operands[0]; |
| rtx src_reg; |
| rtx dest_reg; |
| |
| if (constp && bytes <= 0) |
| return; |
| |
| if (align > UNITS_PER_WORD) |
| align = UNITS_PER_WORD; |
| |
| /* Move the address into scratch registers. */ |
| dest_reg = copy_addr_to_reg (XEXP (orig_dest, 0)); |
| src_reg = copy_addr_to_reg (XEXP (orig_src, 0)); |
| |
| if (TARGET_MEMCPY) |
| block_move_call (dest_reg, src_reg, bytes_rtx); |
| |
| else if (constp && bytes <= 2*MAX_MOVE_BYTES) |
| emit_insn (gen_movstrsi_internal (change_address (orig_dest, BLKmode, |
| dest_reg), |
| change_address (orig_src, BLKmode, |
| src_reg), |
| bytes_rtx, align_rtx)); |
| |
| else if (constp && align >= UNITS_PER_WORD && optimize) |
| block_move_loop (dest_reg, src_reg, bytes, align, orig_dest, orig_src); |
| |
| else if (constp && optimize) |
| { |
| /* If the alignment is not word aligned, generate a test at |
| runtime, to see whether things wound up aligned, and we |
| can use the faster lw/sw instead ulw/usw. */ |
| |
| rtx temp = gen_reg_rtx (Pmode); |
| rtx aligned_label = gen_label_rtx (); |
| rtx join_label = gen_label_rtx (); |
| int leftover = bytes % MAX_MOVE_BYTES; |
| |
| bytes -= leftover; |
| |
| if (TARGET_LONG64) |
| { |
| emit_insn (gen_iordi3 (temp, src_reg, dest_reg)); |
| emit_insn (gen_anddi3 (temp, temp, GEN_INT (UNITS_PER_WORD-1))); |
| emit_insn (gen_cmpdi (temp, const0_rtx)); |
| } |
| else |
| { |
| emit_insn (gen_iorsi3 (temp, src_reg, dest_reg)); |
| emit_insn (gen_andsi3 (temp, temp, GEN_INT (UNITS_PER_WORD-1))); |
| emit_insn (gen_cmpsi (temp, const0_rtx)); |
| } |
| emit_jump_insn (gen_beq (aligned_label)); |
| |
| /* Unaligned loop. */ |
| block_move_loop (dest_reg, src_reg, bytes, 1, orig_dest, orig_src); |
| emit_jump_insn (gen_jump (join_label)); |
| emit_barrier (); |
| |
| /* Aligned loop. */ |
| emit_label (aligned_label); |
| block_move_loop (dest_reg, src_reg, bytes, UNITS_PER_WORD, orig_dest, |
| orig_src); |
| emit_label (join_label); |
| |
| /* Bytes at the end of the loop. */ |
| if (leftover) |
| emit_insn (gen_movstrsi_internal (change_address (orig_dest, BLKmode, |
| dest_reg), |
| change_address (orig_src, BLKmode, |
| src_reg), |
| GEN_INT (leftover), |
| GEN_INT (align))); |
| } |
| |
| else |
| block_move_call (dest_reg, src_reg, bytes_rtx); |
| } |
| |
| |
| /* Emit load/stores for a small constant block_move. |
| |
| operands[0] is the memory address of the destination. |
| operands[1] is the memory address of the source. |
| operands[2] is the number of bytes to move. |
| operands[3] is the alignment. |
| operands[4] is a temp register. |
| operands[5] is a temp register. |
| ... |
| operands[3+num_regs] is the last temp register. |
| |
| The block move type can be one of the following: |
| BLOCK_MOVE_NORMAL Do all of the block move. |
| BLOCK_MOVE_NOT_LAST Do all but the last store. |
| BLOCK_MOVE_LAST Do just the last store. */ |
| |
| char * |
| output_block_move (insn, operands, num_regs, move_type) |
| rtx insn; |
| rtx operands[]; |
| int num_regs; |
| enum block_move_type move_type; |
| { |
| rtx dest_reg = XEXP (operands[0], 0); |
| rtx src_reg = XEXP (operands[1], 0); |
| int bytes = INTVAL (operands[2]); |
| int align = INTVAL (operands[3]); |
| int num = 0; |
| int offset = 0; |
| int use_lwl_lwr = FALSE; |
| int last_operand = num_regs+4; |
| int safe_regs = 4; |
| int i; |
| rtx xoperands[10]; |
| |
| struct { |
| char *load; /* load insn without nop */ |
| char *load_nop; /* load insn with trailing nop */ |
| char *store; /* store insn */ |
| char *final; /* if last_store used: NULL or swr */ |
| char *last_store; /* last store instruction */ |
| int offset; /* current offset */ |
| enum machine_mode mode; /* mode to use on (MEM) */ |
| } load_store[4]; |
| |
| /* Detect a bug in GCC, where it can give us a register |
| the same as one of the addressing registers and reduce |
| the number of registers available. */ |
| for (i = 4; |
| i < last_operand && safe_regs < (sizeof(xoperands) / sizeof(xoperands[0])); |
| i++) |
| { |
| if (!reg_mentioned_p (operands[i], operands[0]) |
| && !reg_mentioned_p (operands[i], operands[1])) |
| |
| xoperands[safe_regs++] = operands[i]; |
| } |
| |
| if (safe_regs < last_operand) |
| { |
| xoperands[0] = operands[0]; |
| xoperands[1] = operands[1]; |
| xoperands[2] = operands[2]; |
| xoperands[3] = operands[3]; |
| return output_block_move (insn, xoperands, safe_regs-4, move_type); |
| } |
| |
| /* If we are given global or static addresses, and we would be |
| emitting a few instructions, try to save time by using a |
| temporary register for the pointer. */ |
| /* ??? The SGI Irix6 assembler fails when a SYMBOL_REF is used in |
| an ldl/ldr instruction pair. We play it safe, and always move |
| constant addresses into registers when generating N32/N64 code, just |
| in case we might emit an unaligned load instruction. */ |
| if (num_regs > 2 && (bytes > 2*align || move_type != BLOCK_MOVE_NORMAL |
| || mips_abi == ABI_N32 || mips_abi == ABI_64)) |
| { |
| if (CONSTANT_P (src_reg)) |
| { |
| if (TARGET_STATS) |
| mips_count_memory_refs (operands[1], 1); |
| |
| src_reg = operands[ 3 + num_regs-- ]; |
| if (move_type != BLOCK_MOVE_LAST) |
| { |
| xoperands[1] = operands[1]; |
| xoperands[0] = src_reg; |
| if (Pmode == DImode) |
| output_asm_insn ("dla\t%0,%1", xoperands); |
| else |
| output_asm_insn ("la\t%0,%1", xoperands); |
| } |
| } |
| |
| if (CONSTANT_P (dest_reg)) |
| { |
| if (TARGET_STATS) |
| mips_count_memory_refs (operands[0], 1); |
| |
| dest_reg = operands[ 3 + num_regs-- ]; |
| if (move_type != BLOCK_MOVE_LAST) |
| { |
| xoperands[1] = operands[0]; |
| xoperands[0] = dest_reg; |
| if (Pmode == DImode) |
| output_asm_insn ("dla\t%0,%1", xoperands); |
| else |
| output_asm_insn ("la\t%0,%1", xoperands); |
| } |
| } |
| } |
| |
| /* ??? We really shouldn't get any LO_SUM addresses here, because they |
| are not offsettable, however, offsettable_address_p says they are |
| offsettable. I think this is a bug in offsettable_address_p. |
| For expediency, we fix this by just loading the address into a register |
| if we happen to get one. */ |
| |
| if (GET_CODE (src_reg) == LO_SUM) |
| { |
| src_reg = operands[ 3 + num_regs-- ]; |
| if (move_type != BLOCK_MOVE_LAST) |
| { |
| xoperands[2] = XEXP (XEXP (operands[1], 0), 1); |
| xoperands[1] = XEXP (XEXP (operands[1], 0), 0); |
| xoperands[0] = src_reg; |
| if (Pmode == DImode) |
| output_asm_insn ("daddiu\t%0,%1,%%lo(%2)", xoperands); |
| else |
| output_asm_insn ("addiu\t%0,%1,%%lo(%2)", xoperands); |
| } |
| } |
| |
| if (GET_CODE (dest_reg) == LO_SUM) |
| { |
| dest_reg = operands[ 3 + num_regs-- ]; |
| if (move_type != BLOCK_MOVE_LAST) |
| { |
| xoperands[2] = XEXP (XEXP (operands[0], 0), 1); |
| xoperands[1] = XEXP (XEXP (operands[0], 0), 0); |
| xoperands[0] = dest_reg; |
| if (Pmode == DImode) |
| output_asm_insn ("daddiu\t%0,%1,%%lo(%2)", xoperands); |
| else |
| output_asm_insn ("addiu\t%0,%1,%%lo(%2)", xoperands); |
| } |
| } |
| |
| if (num_regs > (sizeof (load_store) / sizeof (load_store[0]))) |
| num_regs = (sizeof (load_store) / sizeof (load_store[0])); |
| |
| else if (num_regs < 1) |
| abort_with_insn (insn, "Cannot do block move, not enough scratch registers"); |
| |
| while (bytes > 0) |
| { |
| load_store[num].offset = offset; |
| |
| if (TARGET_64BIT && bytes >= 8 && align >= 8) |
| { |
| load_store[num].load = "ld\t%0,%1"; |
| load_store[num].load_nop = "ld\t%0,%1%#"; |
| load_store[num].store = "sd\t%0,%1"; |
| load_store[num].last_store = "sd\t%0,%1"; |
| load_store[num].final = (char *)0; |
| load_store[num].mode = DImode; |
| offset += 8; |
| bytes -= 8; |
| } |
| |
| else if (TARGET_64BIT && bytes >= 8) |
| { |
| if (BYTES_BIG_ENDIAN) |
| { |
| load_store[num].load = "ldl\t%0,%1\n\tldr\t%0,%2"; |
| load_store[num].load_nop = "ldl\t%0,%1\n\tldr\t%0,%2%#"; |
| load_store[num].store = "sdl\t%0,%1\n\tsdr\t%0,%2"; |
| load_store[num].last_store = "sdr\t%0,%2"; |
| load_store[num].final = "sdl\t%0,%1"; |
| } |
| else |
| { |
| load_store[num].load = "ldl\t%0,%2\n\tldr\t%0,%1"; |
| load_store[num].load_nop = "ldl\t%0,%2\n\tldr\t%0,%1%#"; |
| load_store[num].store = "sdl\t%0,%2\n\tsdr\t%0,%1"; |
| load_store[num].last_store = "sdr\t%0,%1"; |
| load_store[num].final = "sdl\t%0,%2"; |
| } |
| load_store[num].mode = DImode; |
| offset += 8; |
| bytes -= 8; |
| use_lwl_lwr = TRUE; |
| } |
| |
| else if (bytes >= 4 && align >= 4) |
| { |
| load_store[num].load = "lw\t%0,%1"; |
| load_store[num].load_nop = "lw\t%0,%1%#"; |
| load_store[num].store = "sw\t%0,%1"; |
| load_store[num].last_store = "sw\t%0,%1"; |
| load_store[num].final = (char *)0; |
| load_store[num].mode = SImode; |
| offset += 4; |
| bytes -= 4; |
| } |
| |
| else if (bytes >= 4) |
| { |
| if (BYTES_BIG_ENDIAN) |
| { |
| load_store[num].load = "lwl\t%0,%1\n\tlwr\t%0,%2"; |
| load_store[num].load_nop = "lwl\t%0,%1\n\tlwr\t%0,%2%#"; |
| load_store[num].store = "swl\t%0,%1\n\tswr\t%0,%2"; |
| load_store[num].last_store = "swr\t%0,%2"; |
| load_store[num].final = "swl\t%0,%1"; |
| } |
| else |
| { |
| load_store[num].load = "lwl\t%0,%2\n\tlwr\t%0,%1"; |
| load_store[num].load_nop = "lwl\t%0,%2\n\tlwr\t%0,%1%#"; |
| load_store[num].store = "swl\t%0,%2\n\tswr\t%0,%1"; |
| load_store[num].last_store = "swr\t%0,%1"; |
| load_store[num].final = "swl\t%0,%2"; |
| } |
| load_store[num].mode = SImode; |
| offset += 4; |
| bytes -= 4; |
| use_lwl_lwr = TRUE; |
| } |
| |
| else if (bytes >= 2 && align >= 2) |
| { |
| load_store[num].load = "lh\t%0,%1"; |
| load_store[num].load_nop = "lh\t%0,%1%#"; |
| load_store[num].store = "sh\t%0,%1"; |
| load_store[num].last_store = "sh\t%0,%1"; |
| load_store[num].final = (char *)0; |
| load_store[num].mode = HImode; |
| offset += 2; |
| bytes -= 2; |
| } |
| |
| else |
| { |
| load_store[num].load = "lb\t%0,%1"; |
| load_store[num].load_nop = "lb\t%0,%1%#"; |
| load_store[num].store = "sb\t%0,%1"; |
| load_store[num].last_store = "sb\t%0,%1"; |
| load_store[num].final = (char *)0; |
| load_store[num].mode = QImode; |
| offset++; |
| bytes--; |
| } |
| |
| if (TARGET_STATS && move_type != BLOCK_MOVE_LAST) |
| { |
| dslots_load_total++; |
| dslots_load_filled++; |
| |
| if (CONSTANT_P (src_reg)) |
| mips_count_memory_refs (src_reg, 1); |
| |
| if (CONSTANT_P (dest_reg)) |
| mips_count_memory_refs (dest_reg, 1); |
| } |
| |
| /* Emit load/stores now if we have run out of registers or are |
| at the end of the move. */ |
| |
| if (++num == num_regs || bytes == 0) |
| { |
| /* If only load/store, we need a NOP after the load. */ |
| if (num == 1) |
| { |
| load_store[0].load = load_store[0].load_nop; |
| if (TARGET_STATS && move_type != BLOCK_MOVE_LAST) |
| dslots_load_filled--; |
| } |
| |
| if (move_type != BLOCK_MOVE_LAST) |
| { |
| for (i = 0; i < num; i++) |
| { |
| int offset; |
| |
| if (!operands[i+4]) |
| abort (); |
| |
| if (GET_MODE (operands[i+4]) != load_store[i].mode) |
| operands[i+4] = gen_rtx (REG, load_store[i].mode, REGNO (operands[i+4])); |
| |
| offset = load_store[i].offset; |
| xoperands[0] = operands[i+4]; |
| xoperands[1] = gen_rtx (MEM, load_store[i].mode, |
| plus_constant (src_reg, offset)); |
| |
| if (use_lwl_lwr) |
| { |
| int extra_offset; |
| extra_offset = GET_MODE_SIZE (load_store[i].mode) - 1; |
| xoperands[2] = gen_rtx (MEM, load_store[i].mode, |
| plus_constant (src_reg, |
| extra_offset |
| + offset)); |
| } |
| |
| output_asm_insn (load_store[i].load, xoperands); |
| } |
| } |
| |
| for (i = 0; i < num; i++) |
| { |
| int last_p = (i == num-1 && bytes == 0); |
| int offset = load_store[i].offset; |
| |
| xoperands[0] = operands[i+4]; |
| xoperands[1] = gen_rtx (MEM, load_store[i].mode, |
| plus_constant (dest_reg, offset)); |
| |
| |
| if (use_lwl_lwr) |
| { |
| int extra_offset; |
| extra_offset = GET_MODE_SIZE (load_store[i].mode) - 1; |
| xoperands[2] = gen_rtx (MEM, load_store[i].mode, |
| plus_constant (dest_reg, |
| extra_offset |
| + offset)); |
| } |
| |
| if (move_type == BLOCK_MOVE_NORMAL) |
| output_asm_insn (load_store[i].store, xoperands); |
| |
| else if (move_type == BLOCK_MOVE_NOT_LAST) |
| { |
| if (!last_p) |
| output_asm_insn (load_store[i].store, xoperands); |
| |
| else if (load_store[i].final != (char *)0) |
| output_asm_insn (load_store[i].final, xoperands); |
| } |
| |
| else if (last_p) |
| output_asm_insn (load_store[i].last_store, xoperands); |
| } |
| |
| num = 0; /* reset load_store */ |
| use_lwl_lwr = FALSE; |
| } |
| } |
| |
| return ""; |
| } |
| |
| |
| /* Argument support functions. */ |
| |
| /* Initialize CUMULATIVE_ARGS for a function. */ |
| |
| void |
| init_cumulative_args (cum, fntype, libname) |
| CUMULATIVE_ARGS *cum; /* argument info to initialize */ |
| tree fntype; /* tree ptr for function decl */ |
| rtx libname; /* SYMBOL_REF of library name or 0 */ |
| { |
| static CUMULATIVE_ARGS zero_cum; |
| tree param, next_param; |
| |
| if (TARGET_DEBUG_E_MODE) |
| { |
| fprintf (stderr, "\ninit_cumulative_args, fntype = 0x%.8lx", (long)fntype); |
| if (!fntype) |
| fputc ('\n', stderr); |
| |
| else |
| { |
| tree ret_type = TREE_TYPE (fntype); |
| fprintf (stderr, ", fntype code = %s, ret code = %s\n", |
| tree_code_name[ (int)TREE_CODE (fntype) ], |
| tree_code_name[ (int)TREE_CODE (ret_type) ]); |
| } |
| } |
| |
| *cum = zero_cum; |
| |
| /* Determine if this function has variable arguments. This is |
| indicated by the last argument being 'void_type_mode' if there |
| are no variable arguments. The standard MIPS calling sequence |
| passes all arguments in the general purpose registers in this |
| case. */ |
| |
| for (param = (fntype) ? TYPE_ARG_TYPES (fntype) : 0; |
| param != (tree)0; |
| param = next_param) |
| { |
| next_param = TREE_CHAIN (param); |
| if (next_param == (tree)0 && TREE_VALUE (param) != void_type_node) |
| cum->gp_reg_found = 1; |
| } |
| } |
| |
| /* Advance the argument to the next argument position. */ |
| |
| void |
| function_arg_advance (cum, mode, type, named) |
| CUMULATIVE_ARGS *cum; /* current arg information */ |
| enum machine_mode mode; /* current arg mode */ |
| tree type; /* type of the argument or 0 if lib support */ |
| int named; /* whether or not the argument was named */ |
| { |
| if (TARGET_DEBUG_E_MODE) |
| fprintf (stderr, |
| "function_adv( {gp reg found = %d, arg # = %2d, words = %2d}, %4s, 0x%.8x, %d )\n\n", |
| cum->gp_reg_found, cum->arg_number, cum->arg_words, GET_MODE_NAME (mode), |
| type, named); |
| |
| cum->arg_number++; |
| switch (mode) |
| { |
| case VOIDmode: |
| break; |
| |
| default: |
| if (GET_MODE_CLASS (mode) != MODE_COMPLEX_INT |
| && GET_MODE_CLASS (mode) != MODE_COMPLEX_FLOAT) |
| abort (); |
| cum->gp_reg_found = 1; |
| cum->arg_words += ((GET_MODE_SIZE (mode) + UNITS_PER_WORD - 1) |
| / UNITS_PER_WORD); |
| break; |
| |
| case BLKmode: |
| cum->gp_reg_found = 1; |
| cum->arg_words += ((int_size_in_bytes (type) + UNITS_PER_WORD - 1) |
| / UNITS_PER_WORD); |
| break; |
| |
| case SFmode: |
| if (mips_abi == ABI_EABI && ! TARGET_SOFT_FLOAT) |
| cum->fp_arg_words++; |
| else |
| cum->arg_words++; |
| break; |
| |
| case DFmode: |
| if (mips_abi == ABI_EABI && ! TARGET_SOFT_FLOAT && ! TARGET_SINGLE_FLOAT) |
| cum->fp_arg_words += (TARGET_64BIT ? 1 : 2); |
| else |
| cum->arg_words += (TARGET_64BIT ? 1 : 2); |
| break; |
| |
| case DImode: |
| cum->gp_reg_found = 1; |
| cum->arg_words += (TARGET_64BIT ? 1 : 2); |
| break; |
| |
| case QImode: |
| case HImode: |
| case SImode: |
| cum->gp_reg_found = 1; |
| cum->arg_words++; |
| break; |
| } |
| } |
| |
| /* Return an RTL expression containing the register for the given mode, |
| or 0 if the argument is to be passed on the stack. */ |
| |
| struct rtx_def * |
| function_arg (cum, mode, type, named) |
| CUMULATIVE_ARGS *cum; /* current arg information */ |
| enum machine_mode mode; /* current arg mode */ |
| tree type; /* type of the argument or 0 if lib support */ |
| int named; /* != 0 for normal args, == 0 for ... args */ |
| { |
| rtx ret; |
| int regbase = -1; |
| int bias = 0; |
| int *arg_words = &cum->arg_words; |
| int struct_p = ((type != (tree)0) |
| && (TREE_CODE (type) == RECORD_TYPE |
| || TREE_CODE (type) == UNION_TYPE)); |
| |
| if (TARGET_DEBUG_E_MODE) |
| fprintf (stderr, |
| "function_arg( {gp reg found = %d, arg # = %2d, words = %2d}, %4s, 0x%.8x, %d ) = ", |
| cum->gp_reg_found, cum->arg_number, cum->arg_words, GET_MODE_NAME (mode), |
| type, named); |
| |
| cum->last_arg_fp = 0; |
| switch (mode) |
| { |
| case SFmode: |
| if (mips_abi == ABI_32) |
| { |
| if (cum->gp_reg_found || cum->arg_number >= 2 || TARGET_SOFT_FLOAT) |
| regbase = GP_ARG_FIRST; |
| else |
| { |
| regbase = FP_ARG_FIRST; |
| /* If the first arg was a float in a floating point register, |
| then set bias to align this float arg properly. */ |
| if (cum->arg_words == 1) |
| bias = 1; |
| } |
| } |
| else if (mips_abi == ABI_EABI && ! TARGET_SOFT_FLOAT) |
| { |
| if (! TARGET_64BIT) |
| cum->fp_arg_words += cum->fp_arg_words & 1; |
| cum->last_arg_fp = 1; |
| arg_words = &cum->fp_arg_words; |
| regbase = FP_ARG_FIRST; |
| } |
| else |
| regbase = (TARGET_SOFT_FLOAT || ! named ? GP_ARG_FIRST : FP_ARG_FIRST); |
| break; |
| |
| case DFmode: |
| if (! TARGET_64BIT) |
| { |
| if (mips_abi == ABI_EABI |
| && ! TARGET_SOFT_FLOAT |
| && ! TARGET_SINGLE_FLOAT) |
| cum->fp_arg_words += cum->fp_arg_words & 1; |
| else |
| cum->arg_words += cum->arg_words & 1; |
| } |
| if (mips_abi == ABI_32) |
| regbase = ((cum->gp_reg_found |
| || TARGET_SOFT_FLOAT |
| || TARGET_SINGLE_FLOAT |
| || cum->arg_number >= 2) |
| ? GP_ARG_FIRST |
| : FP_ARG_FIRST); |
| else if (mips_abi == ABI_EABI |
| && ! TARGET_SOFT_FLOAT |
| && ! TARGET_SINGLE_FLOAT) |
| |