From cee98a590e92a137da73b0e2bb04eb77aa0011b8 Mon Sep 17 00:00:00 2001 From: Michael Meissner Date: Thu, 6 Feb 1992 20:09:50 +0000 Subject: [PATCH] Initial revision From-SVN: r284 --- gcc/config/mips/mips.c | 3852 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3852 insertions(+) create mode 100644 gcc/config/mips/mips.c diff --git a/gcc/config/mips/mips.c b/gcc/config/mips/mips.c new file mode 100644 index 00000000000..0e6d2b2f02b --- /dev/null +++ b/gcc/config/mips/mips.c @@ -0,0 +1,3852 @@ +/* Subroutines for insn-output.c for MIPS + Contributed by A. Lichnewsky, lich@inria.inria.fr. + Changes by Michael Meissner, meissner@osf.org. + Copyright (C) 1989, 1990, 1991 Free Software Foundation, Inc. + +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, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#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 +#include +#include +#include +#include +#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 + +#ifdef USG +#include "gstab.h" /* If doing DBX on sysV, use our own stab.h. */ +#else +#include /* 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 char *getenv (); + +extern char *permalloc (); +extern void debug_rtx (); +extern void abort_with_insn (); +extern rtx copy_to_reg (); +extern rtx adj_offsettable_operand (); +extern int offsettable_address_p (); +extern tree lookup_name (); + +extern rtx gen_movqi (); +extern rtx gen_movhi (); +extern rtx gen_movsi (); +extern rtx gen_movsi_ulw (); +extern rtx gen_movsi_usw (); +extern rtx gen_addsi3 (); +extern rtx gen_iorsi3 (); +extern rtx gen_andsi3 (); +extern rtx gen_bne (); +extern rtx gen_beq (); +extern rtx gen_cmpsi (); +extern rtx gen_jump (); + +extern char call_used_regs[]; +extern FILE *asm_out_file; +extern tree current_function_decl; +extern char **save_argv; +extern char *version_string; +extern char *language_string; + +/* 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 of the number of functions created so far, in order to make + unique labels for omitting the frame pointer. */ +int number_functions_processed = 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 statment 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; + +/* Global half-pic flag. */ +int flag_half_pic; + +/* 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 current function. */ +char *current_function_name; + +/* 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 supress 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; + +/* which cpu are we scheduling for */ +enum processor_type mips_cpu; + +/* which instruction set architecture to use. */ +int mips_isa; + +/* Strings to hold which cpu and instruction set architecture to use. */ +char *mips_cpu_string; /* for -mcpu= */ +char *mips_isa_string; /* for -mips{1,2,3} */ + +/* Array to RTX class classification. At present, we care about + whether the operator is an add-type operator, or a divide/modulus, + and if divide/modulus, whether it is unsigned. This is for the + peephole code. */ +char mips_rtx_classify[NUM_RTX_CODE]; + +/* 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; + +/* 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", "$fcr31" +}; + +/* Mips software names for the registers, used to overwrite the + mips_reg_names array. */ + +char mips_sw_reg_names[][8] = +{ + "$0", "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", "$fcr31" +}; + +/* 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, ST_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 an integer which is too big to + be loaded with one instruction. */ + +int +large_int (op, mode) + rtx op; + enum machine_mode mode; +{ + long value; + + if (GET_CODE (op) != CONST_INT) + return FALSE; + + value = INTVAL (op); + if ((value & 0xffff0000) == 0) /* ior reg,$r0,value */ + return FALSE; + + if ((value & 0xffff0000) == 0xffff0000) /* 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)) + { + case CONST_INT: + return (INTVAL (op) == 0); + + case CONST_DOUBLE: + if (CONST_DOUBLE_HIGH (op) != 0 || CONST_DOUBLE_LOW (op) != 0) + return FALSE; + + return TRUE; + + case REG: + case SUBREG: + return register_operand (op, mode); + } + + return FALSE; +} + +/* Return truth value of whether OP is one of the special multiply/divide + registers (hi, lo). */ + +int +md_register_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return (GET_MODE_CLASS (mode) == MODE_INT + && GET_CODE (op) == REG + && MD_REG_P (REGNO (op))); +} + +/* 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; +{ + if (GET_CODE (op) != CONST_DOUBLE) + return FALSE; + + if (mode == DImode) + return TRUE; + + if (mode != SFmode && mode != DFmode) + return FALSE; + + if (CONST_DOUBLE_HIGH (op) == 0 && CONST_DOUBLE_LOW (op) == 0) + return TRUE; + +#if HOST_FLOAT_FORMAT == TARGET_FLOAT_FORMAT + if (TARGET_MIPS_AS) /* gas doesn't like li.d/li.s yet */ + { + union { double d; int i[2]; } u; + double d; + + u.i[0] = CONST_DOUBLE_LOW (op); + u.i[1] = CONST_DOUBLE_HIGH (op); + d = u.d; + + if (d != d) + return FALSE; /* NAN */ + + if (d < 0.0) + d = - d; + + /* Rather than trying to get the accuracy down to the last bit, + just use approximate ranges. */ + + if (mode == DFmode && d > 1.0e-300 && d < 1.0e300) + return TRUE; + + if (mode == SFmode && d > 1.0e-38 && d < 1.0e+38) + return TRUE; + } +#endif + + return FALSE; +} + +/* 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; + int offset = 0; + + /* Eliminate non-memory operations */ + if (GET_CODE (op) != MEM) + return FALSE; + + /* dword operations really put out 2 instructions, so eliminate them. */ + if (GET_MODE_SIZE (GET_MODE (op)) > (HAVE_64BIT_P () ? 8 : 4)) + return FALSE; + + /* Decode the address now. */ + addr = XEXP (op, 0); + switch (GET_CODE (addr)) + { + case REG: + 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 (mips_section_threshold == 0 || !optimize || !TARGET_GP_OPT) + return FALSE; + + addr = eliminate_constant_term (addr, &offset); + if (GET_CODE (op) != SYMBOL_REF) + return FALSE; + + /* fall through */ + + case SYMBOL_REF: + /* let's be paranoid.... */ + if (offset < 0 || offset > 0xffff) + return FALSE; + + return SYMBOL_REF_FLAG (addr); +#endif + } + + return FALSE; +} + +/* Return true if the address is suitable for function call. */ + +int +call_memory_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + rtx addr; + enum rtx_code code; + + if (GET_CODE (op) != MEM) + return FALSE; + + addr = XEXP (op, 0); + code = GET_CODE (addr); + + if (GET_MODE (addr) != FUNCTION_MODE) + return FALSE; + + if (code == REG || code == SUBREG) + return TRUE; + + if (CONSTANT_ADDRESS_P (addr)) + return TRUE; + + 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 (classify_op (op, mode) & CLASS_EQUALITY_OP) != 0; +} + +/* 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 (classify_op (op, mode) & CLASS_CMP_OP) != 0; +} + + +/* Genrecog does not take the type of match_operator into consideration, + and would complain about two patterns being the same if the same + function is used, so make it believe they are different. */ + +int +cmp2_op (op, mode) + rtx op; + enum machine_mode mode; +{ + if (mode != GET_MODE (op)) + return FALSE; + + return (classify_op (op, mode) & CLASS_CMP_OP) != 0; +} + +/* Return true if the code is an unsigned relational operations (LEU, etc.) */ + +int +uns_cmp_op (op,mode) + rtx op; + enum machine_mode mode; +{ + if (mode != GET_MODE (op)) + return FALSE; + + return (classify_op (op, mode) & CLASS_UNS_CMP_OP) == CLASS_UNS_CMP_OP; +} + +/* Return true if the code is a relational operation FP can use. */ + +int +fcmp_op (op, mode) + rtx op; + enum machine_mode mode; +{ + if (mode != GET_MODE (op)) + return FALSE; + + return (classify_op (op, mode) & CLASS_FCMP_OP) != 0; +} + + +/* Return an operand string if the given instruction's delay slot or + wrap it in a .set noreorder section. This is for filling delay + slots on load type instructions under GAS, which does no reordering + on its own. For the MIPS assembler, all we do is update the 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. */ + +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) + 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; + mips_load_reg2 = (mode == DImode || mode == DFmode) + ? gen_rtx (REG, SImode, REGNO (set_reg) + 1) + : (rtx)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; + } + + if (TARGET_GAS && set_noreorder++ == 0) + fputs ("\t.set\tnoreorder\n", asm_out_file); + + return ret; +} + + +/* Determine whether a memory reference takes one (based off of the GP pointer), + two (normal), or three (label + reg) instructins, 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)) + { + case REG: + case CONST_INT: + 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 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); + } + + 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; + ret = "mf%1\t%0"; + } + + else + { + delay = DELAY_LOAD; + if (FP_REG_P (regno1)) + ret = "mfc1\t%0,%1"; + + else if (regno1 == FPSW_REGNUM) + 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; + ret = "mt%0\t%1"; + } + } + + else if (regno0 == FPSW_REGNUM) + { + 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)) + { + case SFmode: ret = "lw\t%0,%1"; break; + case SImode: ret = "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) + { + if (INTVAL (op1) == 0) + { + if (GP_REG_P (regno0)) + ret = "move\t%0,%z1"; + + else if (FP_REG_P (regno0)) + { + delay = DELAY_LOAD; + return "mtc1\t%z1,%0"; + } + } + + else if (GP_REG_P (regno0)) + { + if ((INTVAL (operands[1]) & 0x0000ffff) == 0) + ret = "lui\t%0,(%X1)>>16"; + else + ret = "li\t%0,%1"; + } + } + + else if (code1 == CONST_DOUBLE && mode == SFmode) + { + if (CONST_DOUBLE_HIGH (op1) == 0 && CONST_DOUBLE_LOW (op1) == 0) + { + 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) + ret = "la\t%0,%a1"; + + else if (code1 == SYMBOL_REF || code1 == CONST) + { + if (TARGET_STATS) + mips_count_memory_refs (op1, 1); + + if (flag_half_pic && !mips_constant_address_p (op1)) + { + delay = DELAY_LOAD; + ret = "la\t%0,%a1\t\t# pic reference"; + } + else + 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 (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) + { + 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) + { + 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 && CONST_DOUBLE_HIGH (op1) == 0 && CONST_DOUBLE_LOW (op1) == 0) + { + switch (mode) + { + 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); + } + + while (code1 == SUBREG) + { + subreg_word1 += SUBREG_WORD (op1); + op1 = SUBREG_REG (op1); + code1 = GET_CODE (op1); + } + + 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; + ret = (TARGET_FLOAT64) + ? "dmtc1\t%1,%0" + : "mtc1\t%L1,%0\n\tmtc1\t%M1,%D0"; + } + } + + else if (FP_REG_P (regno1)) + { + delay = DELAY_LOAD; + ret = (TARGET_FLOAT64) + ? "dmfc1\t%0,%1" + : "mfc1\t%L0,%1\n\tmfc1\t%M0,%D1"; + } + + else if (MD_REG_P (regno0) && GP_REG_P (regno1)) + { + delay = DELAY_HILO; + ret = "mthi\t%M1\n\tmtlo\t%L1"; + } + + else if (GP_REG_P (regno0) && MD_REG_P (regno1)) + { + delay = DELAY_HILO; + ret = "mfhi\t%M0\n\tmflo\t%L0"; + } + + 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) + { + if (CONST_DOUBLE_HIGH (op1) != 0 || CONST_DOUBLE_LOW (op1) != 0) + { + if (GET_MODE (op1) == DFmode) + { + delay = DELAY_LOAD; + ret = "li.d\t%0,%1"; + } + + else + { + operands[2] = gen_rtx (CONST_INT, VOIDmode, CONST_DOUBLE_LOW (op1)); + operands[3] = gen_rtx (CONST_INT, VOIDmode, CONST_DOUBLE_HIGH (op1)); + ret = "li\t%M0,%3\n\tli\t%L0,%2"; + } + } + + else + { + if (GP_REG_P (regno0)) + ret = "move\t%0,%.\n\tmove\t%D0,%."; + + else if (FP_REG_P (regno0)) + { + delay = DELAY_LOAD; + ret = (TARGET_FLOAT64) + ? "dmtc1\t%.,%0" + : "mtc1\t%.,%0\n\tmtc1\t%.,%D0"; + } + } + } + + else if (code1 == CONST_INT && INTVAL (op1) == 0) + { + if (GP_REG_P (regno0)) + ret = "move\t%0,%.\n\tmove\t%D0,%."; + + else if (FP_REG_P (regno0)) + { + delay = DELAY_LOAD; + ret = (TARGET_FLOAT64) + ? "dmtc1\t%.,%0" + : "mtc1\t%.,%0\n\tmtc1\t%.,%D0"; + } + } + + else if (code1 == CONST_INT && GET_MODE (op0) == DImode && GP_REG_P (regno0)) + { + operands[2] = gen_rtx (CONST_INT, VOIDmode, INTVAL (operands[1]) >= 0 ? 0 : -1); + 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 (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 (code0 == MEM) + { + if (code1 == REG) + { + int regno1 = REGNO (op1) + subreg_word1; + + if (FP_REG_P (regno1)) + ret = "s.d\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_DOUBLE + && CONST_DOUBLE_HIGH (op1) == 0 + && CONST_DOUBLE_LOW (op1) == 0 + && offsettable_address_p (1, DFmode, XEXP (op0, 0))) + { + if (TARGET_FLOAT64) + 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 irrelavent. */ + +int +mips_address_cost (addr) + rtx addr; +{ + int offset; + + switch (GET_CODE (addr)) + { + case LO_SUM: + case HIGH: + return 1; + + case LABEL_REF: + return 2; + + case CONST: + offset = 0; + addr = eliminate_constant_term (addr, &offset); + if (GET_CODE (addr) == LABEL_REF) + return 2; + + if (GET_CODE (addr) != SYMBOL_REF) + return 4; + + if (offset < -32768 || offset > 32767) + 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)) + { + case CONST_INT: + { + int value = INTVAL (plus1); + return (value < -32768 || value > 32767) ? 2 : 1; + } + + case CONST: + case SYMBOL_REF: + case LABEL_REF: + case HIGH: + case LO_SUM: + return mips_address_cost (plus1) + 1; + } + } + } + + return 4; +} + + +/* A C expression that is 1 if the RTX ADDR is a constant which is a + valid address. On most machines, this can be defined as + `CONSTANT_P (ADDR)', but a few machines are more restrictive in + which constant addresses are supported. + + `CONSTANT_P' accepts integer-values expressions whose values are + not explicitly known, such as `symbol_ref', `label_ref', and + `high' expressions and `const' arithmetic expressions, in + addition to `const_int' and `const_double' expressions. */ + +int +mips_constant_address_p (addr) + rtx addr; +{ + int offset; + char *name; + + if (!CONSTANT_P (addr)) + return FALSE; + + /* For -mpic-extern, don't allow any reference to an external in + normal code. Force it to be PIC-ized. */ + + if (flag_half_pic) + { + switch (GET_CODE (addr)) + { + case CONST: + addr = eliminate_constant_term (addr, &offset); + if (GET_CODE (addr) != SYMBOL_REF) + return FALSE; + + /* fall through */ + + case SYMBOL_REF: + name = XSTR (addr, 0); + + /* Skip '*' that appears in front of labels and such. */ + if (name[0] == '*') + name++; + + /* Internally generated labels are not pic. */ + if (name[0] == '$' && name[1] == 'L') + return TRUE; + + return FALSE; + } + } + + return TRUE; +} + + +/* Emit the common code for doing conditional branches. + operand[0] is the label to jump to. + The comparison operands are saved away by cmp{si,sf,df}. */ + +void +gen_conditional_branch (operands, test_code) + rtx operands[]; + enum rtx_code test_code; +{ + enum { + I_EQ, + I_NE, + I_GT, + I_GE, + I_LT, + I_LE, + I_GTU, + I_GEU, + I_LTU, + I_LEU, + I_MAX + } test = I_MAX; + + static enum machine_mode mode_map[(int)CMP_MAX][(int)I_MAX] = { + { /* CMP_SI */ + CC_EQmode, /* eq */ + CC_EQmode, /* ne */ + CCmode, /* gt */ + CCmode, /* ge */ + CCmode, /* lt */ + CCmode, /* le */ + CCmode, /* gtu */ + CCmode, /* geu */ + CCmode, /* ltu */ + CCmode, /* leu */ + }, + { /* CMP_SF */ + CC_FPmode, /* eq */ + CC_FPmode, /* ne */ + CC_FPmode, /* gt */ + CC_FPmode, /* ge */ + CC_FPmode, /* lt */ + CC_FPmode, /* le */ + VOIDmode, /* gtu */ + VOIDmode, /* geu */ + VOIDmode, /* ltu */ + VOIDmode, /* leu */ + }, + { /* CMP_DF */ + CC_FPmode, /* eq */ + CC_FPmode, /* ne */ + CC_FPmode, /* gt */ + CC_FPmode, /* ge */ + CC_FPmode, /* lt */ + CC_FPmode, /* le */ + VOIDmode, /* gtu */ + VOIDmode, /* geu */ + VOIDmode, /* ltu */ + VOIDmode, /* leu */ + }, + }; + + enum machine_mode mode; + enum cmp_type type = branch_type; + rtx cmp0 = branch_cmp[0]; + rtx cmp1 = branch_cmp[1]; + rtx label = gen_rtx (LABEL_REF, VOIDmode, operands[0]); + + /* Make normal rtx_code into something we can index from an array */ + switch (test_code) + { + case EQ: test = I_EQ; break; + case NE: test = I_NE; break; + case GT: test = I_GT; break; + case GE: test = I_GE; break; + case LT: test = I_LT; break; + case LE: test = I_LE; break; + case GTU: test = I_GTU; break; + case GEU: test = I_GEU; break; + case LTU: test = I_LTU; break; + case LEU: test = I_LEU; break; + } + + if (test == I_MAX) + { + mode = CCmode; + goto fail; + } + + /* Get the machine mode to use (CCmode, CC_EQmode, or CC_FPmode). */ + mode = mode_map[(int)type][(int)test]; + if (mode == VOIDmode) + goto fail; + + switch (branch_type) + { + default: + goto fail; + + case CMP_SI: + /* Change >, >=, <, <= tests against 0 to use CC_0mode, since we have + special instructions to do these tests directly. */ + + if (mode == CCmode && GET_CODE (cmp1) == CONST_INT && INTVAL (cmp1) == 0) + { + emit_insn (gen_rtx (SET, VOIDmode, cc0_rtx, cmp0)); + mode = CC_0mode; + } + + else if (mode == CCmode && GET_CODE (cmp0) == CONST_INT && INTVAL (cmp0) == 0) + { + emit_insn (gen_rtx (SET, VOIDmode, cc0_rtx, cmp1)); + test_code = reverse_condition (test_code); + mode = CC_0mode; + } + + else + { + /* force args to register for equality comparisons. */ + if (mode == CC_EQmode && GET_CODE (cmp1) == CONST_INT && INTVAL (cmp1) != 0) + cmp1 = force_reg (SImode, cmp1); + + emit_insn (gen_rtx (SET, VOIDmode, + cc0_rtx, + gen_rtx (COMPARE, mode, cmp0, cmp1))); + } + break; + + case CMP_DF: + emit_insn (gen_cmpdf_internal (cmp0, cmp1)); + break; + + case CMP_SF: + emit_insn (gen_cmpsf_internal (cmp0, cmp1)); + break; + } + + /* Generate the jump */ + emit_jump_insn (gen_rtx (SET, VOIDmode, + pc_rtx, + gen_rtx (IF_THEN_ELSE, VOIDmode, + gen_rtx (test_code, mode, cc0_rtx, const0_rtx), + label, + pc_rtx))); + return; + +fail: + abort_with_insn (gen_rtx (test_code, mode, cmp0, cmp1), "bad test"); +} + + +#define UNITS_PER_SHORT (SHORT_TYPE_SIZE / BITS_PER_UNIT) + +/* Internal code to generate the load and store of one word/short/byte. + The load is emitted directly, and the store insn is returned. */ + +static rtx +block_move_load_store (dest_reg, src_reg, p_bytes, p_offset, align) + rtx src_reg; /* register holding source memory addresss */ + rtx dest_reg; /* register holding dest. memory addresss */ + int *p_bytes; /* pointer to # bytes remaining */ + int *p_offset; /* pointer to current offset */ + int align; /* alignment */ +{ + int bytes; /* # bytes remaining */ + int offset; /* offset to use */ + int size; /* size in bytes of load/store */ + enum machine_mode mode; /* mode to use for load/store */ + rtx reg; /* temporary register */ + rtx src_addr; /* source address */ + rtx dest_addr; /* destintation address */ + rtx (*load_func)(); /* function to generate load insn */ + rtx (*store_func)(); /* function to generate destination insn */ + + bytes = *p_bytes; + if (bytes <= 0 || align <= 0) + abort (); + + if (bytes >= UNITS_PER_WORD && align >= UNITS_PER_WORD) + { + mode = SImode; + size = UNITS_PER_WORD; + load_func = gen_movsi; + store_func = gen_movsi; + } + + else if (bytes >= UNITS_PER_WORD) + { + mode = SImode; + size = UNITS_PER_WORD; + load_func = gen_movsi_ulw; + store_func = gen_movsi_usw; + } + + else if (bytes >= UNITS_PER_SHORT && align >= UNITS_PER_SHORT) + { + mode = HImode; + size = UNITS_PER_SHORT; + load_func = gen_movhi; + store_func = gen_movhi; + } + + else + { + mode = QImode; + size = 1; + load_func = gen_movqi; + store_func = gen_movqi; + } + + offset = *p_offset; + *p_offset = offset + size; + *p_bytes = bytes - size; + + if (offset == 0) + { + src_addr = src_reg; + dest_addr = dest_reg; + } + else + { + src_addr = gen_rtx (PLUS, Pmode, src_reg, gen_rtx (CONST_INT, VOIDmode, offset)); + dest_addr = gen_rtx (PLUS, Pmode, dest_reg, gen_rtx (CONST_INT, VOIDmode, offset)); + } + + reg = gen_reg_rtx (mode); + emit_insn ((*load_func) (reg, gen_rtx (MEM, mode, src_addr))); + return (*store_func) (gen_rtx (MEM, mode, dest_addr), reg); +} + + +/* Write a series of loads/stores to move some bytes. Generate load/stores as follows: + + load 1 + load 2 + load 3 + store 1 + load 4 + store 2 + load 5 + store 3 + ... + + This way, no NOP's are needed, except at the end, and only + two temp registers are needed. Two delay slots are used + in deference to the R4000. */ + +static void +block_move_sequence (dest_reg, src_reg, bytes, align) + rtx dest_reg; /* register holding destination address */ + rtx src_reg; /* register holding source address */ + int bytes; /* # bytes to move */ + int align; /* max alignment to assume */ +{ + int offset = 0; + rtx prev2_store = (rtx)0; + rtx prev_store = (rtx)0; + rtx cur_store = (rtx)0; + + while (bytes > 0) + { + /* Is there a store to do? */ + if (prev2_store) + emit_insn (prev2_store); + + prev2_store = prev_store; + prev_store = cur_store; + cur_store = block_move_load_store (dest_reg, src_reg, &bytes, &offset, align); + } + + /* Finish up last three stores. */ + if (prev2_store) + emit_insn (prev2_store); + + if (prev_store) + emit_insn (prev_store); + + if (cur_store) + emit_insn (cur_store); +} + + +/* Write a loop to move a constant number of bytes. Generate load/stores as follows: + + do { + temp1 = src[0]; + temp2 = src[1]; + ... + temp = src[MAX_MOVE_REGS-1]; + src += MAX_MOVE_REGS; + dest[0] = temp1; + dest[1] = temp2; + ... + dest[MAX_MOVE_REGS-1] = temp; + 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) + rtx dest_reg; /* register holding destination address */ + rtx src_reg; /* register holding source address */ + int bytes; /* # bytes to move */ + int align; /* alignment */ +{ + rtx stores[MAX_MOVE_REGS]; + rtx label; + rtx final_src; + rtx bytes_rtx; + int i; + int leftover; + int offset; + + 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_rtx (CONST_INT, VOIDmode, bytes); + + if (bytes > 0x7fff) + { + emit_insn (gen_movsi (final_src, bytes_rtx)); + emit_insn (gen_addsi3 (final_src, final_src, src_reg)); + } + else + emit_insn (gen_addsi3 (final_src, src_reg, bytes_rtx)); + + emit_label (label); + + offset = 0; + for (i = 0; i < MAX_MOVE_REGS; i++) + stores[i] = block_move_load_store (dest_reg, src_reg, &bytes, &offset, align); + + emit_insn (gen_addsi3 (src_reg, src_reg, gen_rtx (CONST_INT, VOIDmode, MAX_MOVE_BYTES))); + for (i = 0; i < MAX_MOVE_REGS; i++) + emit_insn (stores[i]); + + emit_insn (gen_addsi3 (dest_reg, dest_reg, gen_rtx (CONST_INT, VOIDmode, MAX_MOVE_BYTES))); + emit_insn (gen_cmpsi (src_reg, final_src)); + emit_jump_insn (gen_bne (label)); + + if (leftover) + block_move_sequence (dest_reg, src_reg, leftover, align); +} + + +/* 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; +{ +#ifdef TARGET_MEM_FUNCTIONS + emit_library_call (gen_rtx (SYMBOL_REF, Pmode, "memcpy"), 0, + VOIDmode, 3, + dest_reg, Pmode, + src_reg, Pmode, + bytes_rtx, SImode); +#else + emit_library_call (gen_rtx (SYMBOL_REF, Pmode, "bcopy"), 0, + VOIDmode, 3, + src_reg, Pmode, + dest_reg, Pmode, + bytes_rtx, SImode); +#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]; + int constp = (GET_CODE (bytes_rtx) == CONST_INT); + int bytes = (constp ? INTVAL (bytes_rtx) : 0); + int align = INTVAL (operands[3]); + rtx src_reg; + rtx dest_reg; + + if (constp && bytes <= 0) + return; + + /* Move the address into scratch registers. */ + dest_reg = copy_to_reg (XEXP (operands[0], 0)); + src_reg = copy_to_reg (XEXP (operands[1], 0)); + + if (TARGET_MEMCPY) + block_move_call (dest_reg, src_reg, bytes_rtx); + + else if (constp && bytes <= 2*MAX_MOVE_BYTES) + block_move_sequence (dest_reg, src_reg, bytes, align); + + else if (constp && align >= UNITS_PER_WORD && optimize) + block_move_loop (dest_reg, src_reg, bytes, align); + + 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; + + emit_insn (gen_iorsi3 (temp, src_reg, dest_reg)); + emit_insn (gen_andsi3 (temp, temp, gen_rtx (CONST_INT, VOIDmode, 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); + 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); + emit_label (join_label); + + /* Bytes at the end of the loop. */ + if (leftover) + block_move_sequence (dest_reg, src_reg, leftover, align); + } + + else + block_move_call (dest_reg, src_reg, bytes_rtx); +} + + +/* 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 */ +{ + tree param, next_param; + + if (TARGET_DEBUG_E_MODE) + fprintf (stderr, "\ninit_cumulative_args\n"); + + cum->gp_reg_found = 0; + cum->arg_number = 0; + cum->arg_words = 0; + + /* 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; + } + + /* Determine if the function is returning a structure, if so, + advance by one argument. */ + + if (fntype + && (TREE_CODE (fntype) == FUNCTION_TYPE || TREE_CODE (fntype) == METHOD_TYPE) + && TREE_TYPE (fntype) != 0) + { + tree ret_type = TREE_TYPE (fntype); + enum tree_code ret_code = TREE_CODE (ret_type); + + if (ret_code == RECORD_TYPE || ret_code == UNION_TYPE) + { + cum->gp_reg_found = 1; + cum->arg_number = 1; + cum->arg_words = 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 */ +{ + if (TARGET_DEBUG_E_MODE) + fprintf (stderr, + "function_adv( {gp reg found = %d, arg # = %2d, words = %2d}, %4s, 0x%.8x, %d )\n", + cum->gp_reg_found, cum->arg_number, cum->arg_words, GET_MODE_NAME (mode), + type, named); + + cum->arg_number++; + switch (mode) + { + default: + error ("Illegal mode given to function_arg_advance"); + break; + + case VOIDmode: + break; + + case BLKmode: + cum->gp_reg_found = 1; + cum->arg_words += (int_size_in_bytes (type) + 3) / 4; + break; + + case SFmode: + cum->arg_words++; + break; + + case DFmode: + cum->arg_words += 2; + break; + + case DImode: + cum->gp_reg_found = 1; + cum->arg_words += 2; + break; + + case QImode: + case HImode: + case SImode: + cum->gp_reg_found = 1; + cum->arg_words++; + break; + } +} + +/* Return a RTL expression containing the register for the given mode, + or 0 if the argument is too 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 */ +{ + int regbase = -1; + int bias = 0; + + 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); + + switch (mode) + { + default: + error ("Illegal mode given to function_arg"); + break; + + case SFmode: + if (cum->gp_reg_found || cum->arg_number >= 2) + regbase = GP_ARG_FIRST; + else { + regbase = (TARGET_SOFT_FLOAT) ? GP_ARG_FIRST : FP_ARG_FIRST; + if (cum->arg_words == 1) /* first arg was float */ + bias = 1; /* use correct reg */ + } + + break; + + case DFmode: + cum->arg_words += (cum->arg_words & 1); + regbase = (cum->gp_reg_found || TARGET_SOFT_FLOAT) + ? GP_ARG_FIRST + : FP_ARG_FIRST; + break; + + case VOIDmode: + case BLKmode: + case QImode: + case HImode: + case SImode: + regbase = GP_ARG_FIRST; + break; + + case DImode: + cum->arg_words += (cum->arg_words & 1); + regbase = GP_ARG_FIRST; + } + + if (cum->arg_words >= MAX_ARGS_IN_REGISTERS) + { + if (TARGET_DEBUG_E_MODE) + fprintf (stderr, "\n"); + + return 0; + } + + if (regbase == -1) + abort (); + + if (TARGET_DEBUG_E_MODE) + fprintf (stderr, "%s\n", reg_names[regbase + cum->arg_words + bias]); + + return gen_rtx (REG, mode, regbase + cum->arg_words + bias); +} + + +int +function_arg_partial_nregs (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 */ +{ + if (mode == BLKmode && cum->arg_words < MAX_ARGS_IN_REGISTERS) + { + int words = (int_size_in_bytes (type) + 3) / 4; + + if (words + cum->arg_words < MAX_ARGS_IN_REGISTERS) + return 0; /* structure fits in registers */ + + if (TARGET_DEBUG_E_MODE) + fprintf (stderr, "function_arg_partial_nregs = %d\n", + MAX_ARGS_IN_REGISTERS - cum->arg_words); + + return MAX_ARGS_IN_REGISTERS - cum->arg_words; + } + + else if (mode == DImode && cum->arg_words == MAX_ARGS_IN_REGISTERS-1) + { + if (TARGET_DEBUG_E_MODE) + fprintf (stderr, "function_arg_partial_nregs = 1\n"); + + return 1; + } + + return 0; +} + + +/* Print the options used in the assembly file. */ + +static struct {char *name; int value;} target_switches [] + = TARGET_SWITCHES; + +void +print_options (out) + FILE *out; +{ + int line_len; + int len; + int j; + char **p; + int mask = TARGET_DEFAULT; + + /* Allow assembly language comparisons with -mdebug eliminating the + compiler version number and switch lists. */ + + if (TARGET_DEBUG_MODE) + return; + + fprintf (out, "\n # %s %s", language_string, version_string); +#ifdef TARGET_VERSION_INTERNAL + TARGET_VERSION_INTERNAL (out); +#endif +#ifdef __GNUC__ + fprintf (out, " compiled by GNU C\n\n"); +#else + fprintf (out, " compiled by CC\n\n"); +#endif + + fprintf (out, " # Cc1 defaults:"); + line_len = 32767; + for (j = 0; j < sizeof target_switches / sizeof target_switches[0]; j++) + { + if (target_switches[j].name[0] != '\0' + && target_switches[j].value > 0 + && (target_switches[j].value & mask) == target_switches[j].value) + { + mask &= ~ target_switches[j].value; + len = strlen (target_switches[j].name) + 1; + if (len + line_len > 79) + { + line_len = 2; + fputs ("\n #", out); + } + fprintf (out, " -m%s", target_switches[j].name); + line_len += len; + } + } + + fprintf (out, "\n\n # Cc1 arguments (-G value = %d, Cpu = %s, ISA = %d):", + mips_section_threshold, mips_cpu_string, mips_isa); + + line_len = 32767; + for (p = &save_argv[1]; *p != (char *)0; p++) + { + char *arg = *p; + if (*arg == '-') + { + len = strlen (arg) + 1; + if (len + line_len > 79) + { + line_len = 2; + fputs ("\n #", out); + } + fprintf (out, " %s", *p); + line_len += len; + } + } + + fputs ("\n\n", out); +} + + +/* Abort after printing out a specific insn. */ + +void +abort_with_insn (insn, reason) + rtx insn; + char *reason; +{ + error (reason); + debug_rtx (insn); + abort (); +} + +/* Write a message to stderr (for use in macros expanded in files that do not + include stdio.h). */ + +void +trace (s, s1, s2) + char *s, *s1, *s2; +{ + fprintf (stderr, s, s1, s2); +} + + +#ifdef SIGINFO + +#include + +static void +siginfo (signo) + int signo; +{ + char print_pid[50]; + char *argv[4]; + pid_t pid; + int status; + + fprintf (stderr, "compiling '%s' in '%s'\n", + (current_function_name != (char *)0) ? current_function_name : "", + (current_function_file != (char *)0) ? current_function_file : ""); + + /* Spawn a ps to tell about current memory usage, etc. */ + sprintf (print_pid, "-p%d,%d", getpid (), getppid ()); + argv[0] = "ps"; + argv[1] = print_pid; + argv[2] = "-ouser,state,pid,pri,nice,wchan,tt,start,usertime,systime,pcpu,cp,inblock,oublock,vsize,rss,pmem,ucomm"; + argv[3] = (char *)0; + + pid = vfork (); + if (pid == 0) /* child context */ + { + execv ("/usr/bin/ps", argv); + execv ("/usr/sbin/ps", argv); + execvp ("ps", argv); + perror ("ps"); + _exit (1); + } + + else if (pid > 0) /* parent context */ + { + void (*sigint)(int) = signal (SIGINT, SIG_IGN); + void (*sigquit)(int) = signal (SIGQUIT, SIG_IGN); + + (void) waitpid (pid, &status, 0); + + (void) signal (SIGINT, sigint); + (void) signal (SIGQUIT, sigquit); + } + + signal (SIGINFO, siginfo); +} + +#endif /* SIGINFO */ + + +/* Set up the threshold for data to go into the small data area, instead + of the normal data area, and detect any conflicts in the switches. */ + +void +override_options () +{ + register int i, start; + register int regno; + register enum machine_mode mode; + + if (g_switch_set) + mips_section_threshold = g_switch_value; + + else + mips_section_threshold = (TARGET_MIPS_AS) ? 8 : 0; + + /* Identify the processor type */ + if (mips_cpu_string == (char *)0 + || !strcmp (mips_cpu_string, "default") + || !strcmp (mips_cpu_string, "DEFAULT")) + { + mips_cpu_string = "default"; + mips_cpu = PROCESSOR_DEFAULT; + } + + else + { + char *p = mips_cpu_string; + + if (*p == 'r' || *p == 'R') + p++; + + /* Since there is no difference between a R2000 and R3000 in + terms of the scheduler, we collapse them into just an R3000. */ + + mips_cpu = PROCESSOR_DEFAULT; + switch (*p) + { + case '2': + if (!strcmp (p, "2000") || !strcmp (p, "2k") || !strcmp (p, "2K")) + mips_cpu = PROCESSOR_R3000; + break; + + case '3': + if (!strcmp (p, "3000") || !strcmp (p, "3k") || !strcmp (p, "3K")) + mips_cpu = PROCESSOR_R3000; + break; + + case '4': + if (!strcmp (p, "4000") || !strcmp (p, "4k") || !strcmp (p, "4K")) + mips_cpu = PROCESSOR_R4000; + break; + + case '6': + if (!strcmp (p, "6000") || !strcmp (p, "6k") || !strcmp (p, "6K")) + mips_cpu = PROCESSOR_R6000; + break; + } + + if (mips_cpu == PROCESSOR_DEFAULT) + { + error ("bad value (%s) for -mcpu= switch", mips_cpu_string); + mips_cpu_string = "default"; + } + } + + /* Now get the architectural level. */ + if (mips_isa_string == (char *)0) + mips_isa = 1; + + else if (isdigit (*mips_isa_string)) + mips_isa = atoi (mips_isa_string); + + else + { + error ("bad value (%s) for -mips switch", mips_isa_string); + mips_isa = 1; + } + + if (mips_isa < 0 || mips_isa > 3) + error ("-mips%d not supported", mips_isa); + + else if (mips_isa > 1 + && (mips_cpu == PROCESSOR_DEFAULT || mips_cpu == PROCESSOR_R3000)) + error ("-mcpu=%s does not support -mips%d", mips_cpu_string, mips_isa); + + else if (mips_cpu == PROCESSOR_R6000 && mips_isa > 2) + error ("-mcpu=%s does not support -mips%d", mips_cpu_string, mips_isa); + + /* make sure sizes of ints/longs/etc. are ok */ + if (mips_isa < 3) + { + if (TARGET_INT64) + fatal ("Only the r4000 can support 64 bit ints"); + + else if (TARGET_LONG64) + fatal ("Only the r4000 can support 64 bit longs"); + + else if (TARGET_LLONG128) + fatal ("Only the r4000 can support 128 bit long longs"); + + else if (TARGET_FLOAT64) + fatal ("Only the r4000 can support 64 bit fp registers"); + } + else if (TARGET_INT64 || TARGET_LONG64 || TARGET_LLONG128 || TARGET_FLOAT64) + warning ("r4000 64/128 bit types not yet supported"); + + /* Tell halfpic.c that we have half-pic code if we do. */ + if (TARGET_HALF_PIC) + flag_half_pic = TRUE; + + /* -mrnames says to use the MIPS software convention for register + names instead of the hardware names (ie, a0 instead of $4). + We do this by switching the names in mips_reg_names, which the + reg_names points into via the REGISTER_NAMES macro. */ + + if (TARGET_NAME_REGS) + { + if (TARGET_GAS) + { + target_flags &= ~ MASK_NAME_REGS; + error ("Gas does not support the MIPS software register name convention."); + } + else + bcopy ((char *) mips_sw_reg_names, (char *) mips_reg_names, sizeof (mips_reg_names)); + } + + /* If this is OSF/1, set up a SIGINFO handler so we can see what function + is currently being compiled. */ +#ifdef SIGINFO + if (getenv ("GCC_SIGINFO") != (char *)0) + { + struct sigaction action; + action.sa_handler = siginfo; + action.sa_mask = 0; + action.sa_flags = SA_RESTART; + sigaction (SIGINFO, &action, (struct sigaction *)0); + } +#endif + + /* Set up the classificaiton arrays now. */ + mips_rtx_classify[(int)PLUS] = CLASS_ADD_OP; + mips_rtx_classify[(int)MINUS] = CLASS_ADD_OP; + mips_rtx_classify[(int)DIV] = CLASS_DIVMOD_OP; + mips_rtx_classify[(int)MOD] = CLASS_DIVMOD_OP; + mips_rtx_classify[(int)UDIV] = CLASS_DIVMOD_OP | CLASS_UNSIGNED_OP; + mips_rtx_classify[(int)UMOD] = CLASS_DIVMOD_OP | CLASS_UNSIGNED_OP; + mips_rtx_classify[(int)EQ] = CLASS_CMP_OP | CLASS_EQUALITY_OP | CLASS_FCMP_OP; + mips_rtx_classify[(int)NE] = CLASS_CMP_OP | CLASS_EQUALITY_OP | CLASS_FCMP_OP; + mips_rtx_classify[(int)GT] = CLASS_CMP_OP | CLASS_FCMP_OP; + mips_rtx_classify[(int)GE] = CLASS_CMP_OP | CLASS_FCMP_OP; + mips_rtx_classify[(int)LT] = CLASS_CMP_OP | CLASS_FCMP_OP; + mips_rtx_classify[(int)LE] = CLASS_CMP_OP | CLASS_FCMP_OP; + mips_rtx_classify[(int)GTU] = CLASS_CMP_OP | CLASS_UNSIGNED_OP; + mips_rtx_classify[(int)GEU] = CLASS_CMP_OP | CLASS_UNSIGNED_OP; + mips_rtx_classify[(int)LTU] = CLASS_CMP_OP | CLASS_UNSIGNED_OP; + mips_rtx_classify[(int)LEU] = CLASS_CMP_OP | CLASS_UNSIGNED_OP; + + mips_print_operand_punct['?'] = TRUE; + mips_print_operand_punct['#'] = TRUE; + mips_print_operand_punct['&'] = TRUE; + mips_print_operand_punct['!'] = TRUE; + mips_print_operand_punct['*'] = TRUE; + mips_print_operand_punct['@'] = TRUE; + mips_print_operand_punct['.'] = TRUE; + mips_print_operand_punct['('] = TRUE; + mips_print_operand_punct[')'] = TRUE; + mips_print_operand_punct['['] = TRUE; + mips_print_operand_punct[']'] = TRUE; + mips_print_operand_punct['<'] = TRUE; + mips_print_operand_punct['>'] = TRUE; + mips_print_operand_punct['{'] = TRUE; + mips_print_operand_punct['}'] = TRUE; + + mips_char_to_class['d'] = GR_REGS; + mips_char_to_class['f'] = ((TARGET_HARD_FLOAT) ? FP_REGS : NO_REGS); + mips_char_to_class['h'] = HI_REG; + mips_char_to_class['l'] = LO_REG; + mips_char_to_class['s'] = ST_REGS; + mips_char_to_class['x'] = MD_REGS; + mips_char_to_class['y'] = GR_REGS; + + /* Set up array to map GCC register number to debug register number. + Ignore the special purpose register numbers. */ + + for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) + mips_dbx_regno[i] = -1; + + start = GP_DBX_FIRST - GP_REG_FIRST; + for (i = GP_REG_FIRST; i <= GP_REG_LAST; i++) + mips_dbx_regno[i] = i + start; + + start = FP_DBX_FIRST - FP_REG_FIRST; + for (i = FP_REG_FIRST; i <= FP_REG_LAST; i++) + mips_dbx_regno[i] = i + start; + + /* Set up array giving whether a given register can hold a given mode. + At present, restrict ints from being in FP registers, because reload + is a little enthusiastic about storing extra values in FP registers, + and this is not good for things like OS kernels. Also, due to the + manditory delay, it is as fast to load from cached memory as to move + from the FP register. */ + + for (mode = VOIDmode; + mode != MAX_MACHINE_MODE; + mode = (enum machine_mode)((int)mode + 1)) + { + register int size = GET_MODE_SIZE (mode); + register enum mode_class class = GET_MODE_CLASS (mode); + + for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++) + { + register int temp = FALSE; + + if (GP_REG_P (regno)) + temp = ((regno & 1) == 0 || (size <= UNITS_PER_WORD)); + + else if (FP_REG_P (regno)) + temp = ((TARGET_FLOAT64 || ((regno & 1) == 0)) + && (TARGET_DEBUG_H_MODE + || class == MODE_FLOAT + || class == MODE_COMPLEX_FLOAT)); + + else if (MD_REG_P (regno)) + temp = (mode == SImode || (regno == MD_REG_FIRST && mode == DImode)); + + else if (ST_REG_P (regno)) + temp = ((mode == SImode) || (class == MODE_CC)); + + mips_hard_regno_mode_ok[(int)mode][regno] = temp; + } + } +} + + +/* + * If the frame pointer has been eliminated, the offset for an auto + * or argument will be based on the stack pointer. But this is not + * what the debugger expects--it needs to find an offset off of the + * frame pointer (whether it exists or not). So here we turn all + * offsets into those based on the (possibly virtual) frame pointer. + */ + +int +mips_debugger_offset (addr, offset) + rtx addr; + int offset; +{ + int offset2 = 0; + rtx reg = eliminate_constant_term (addr, &offset2); + + if (!offset) + offset = offset2; + + if (reg == stack_pointer_rtx) + offset = offset - ((!current_frame_info.initialized) + ? compute_frame_size (get_frame_size ()) + : current_frame_info.total_size); + + /* Any other register is, we hope, either the frame pointer, + or a pseudo equivalent to the frame pointer. (Assign_parms + copies the arg pointer to a pseudo if ARG_POINTER_REGNUM is + equal to FRAME_POINTER_REGNUM, so references off of the + arg pointer are all off a pseudo.) Seems like all we can + do is to just return OFFSET and hope for the best. */ + + return offset; +} + +/* A C compound statement to output to stdio stream STREAM the + assembler syntax for an instruction operand X. X is an RTL + expression. + + CODE is a value that can be used to specify one of several ways + of printing the operand. It is used when identical operands + must be printed differently depending on the context. CODE + comes from the `%' specification that was used to request + printing of the operand. If the specification was just `%DIGIT' + then CODE is 0; if the specification was `%LTR DIGIT' then CODE + is the ASCII code for LTR. + + If X is a register, this macro should print the register's name. + The names can be found in an array `reg_names' whose type is + `char *[]'. `reg_names' is initialized from `REGISTER_NAMES'. + + When the machine description has a specification `%PUNCT' (a `%' + followed by a punctuation character), this macro is called with + a null pointer for X and the punctuation character for CODE. + + The MIPS specific codes are: + + 'X' X is CONST_INT, prints 32 bits in hexadecimal format = "0x%08x", + 'x' X is CONST_INT, prints 16 bits in hexadecimal format = "0x%04x", + 'd' output integer constant in decimal, + 'z' if the operand is 0, use $0 instead of normal operand. + 'D' print second register of double-word register operand. + 'L' print low-order register of double-word register operand. + 'M' print high-order register of double-word register operand. + 'C' print part of opcode for a branch condition. + 'N' print part of opcode for a branch condition, inverted. + '(' Turn on .set noreorder + ')' Turn on .set reorder + '[' Turn on .set noat + ']' Turn on .set at + '<' Turn on .set nomacro + '>' Turn on .set macro + '{' Turn on .set volatile (not GAS) + '}' Turn on .set novolatile (not GAS) + '&' Turn on .set noreorder if filling delay slots + '*' Turn on both .set noreorder and .set nomacro if filling delay slots + '!' Turn on .set nomacro if filling delay slots + '#' Print nop if in a .set noreorder section. + '?' Print 'l' if we are to use a branch likely instead of normal branch. + '@' Print the name of the assembler temporary register (at or $1). + '.' Print the name of the register with a hard-wired zero (zero or $0). */ + +void +print_operand (file, op, letter) + FILE *file; /* file to write to */ + rtx op; /* operand to print */ + int letter; /* % or 0 */ +{ + register enum rtx_code code; + + if (PRINT_OPERAND_PUNCT_VALID_P (letter)) + { + switch (letter) + { + default: + error ("PRINT_OPERAND: Unknown punctuation '%c'", letter); + break; + + case '?': + if (mips_branch_likely) + putc ('l', file); + break; + + case '@': + fputs (reg_names [GP_REG_FIRST + 1], file); + break; + + case '.': + fputs (reg_names [GP_REG_FIRST + 0], file); + break; + + case '&': + if (final_sequence != 0 && set_noreorder++ == 0) + fputs (".set\tnoreorder\n\t", file); + break; + + case '*': + if (final_sequence != 0) + { + if (set_noreorder++ == 0) + fputs (".set\tnoreorder\n\t", file); + + if (set_nomacro++ == 0) + fputs (".set\tnomacro\n\t", file); + } + break; + + case '!': + if (final_sequence != 0 && set_nomacro++ == 0) + fputs ("\n\t.set\tnomacro", file); + break; + + case '#': + if (set_noreorder != 0) + fputs ("\n\tnop", file); + + else if (TARGET_GAS || TARGET_STATS) + fputs ("\n\t#nop", file); + + break; + + case '(': + if (set_noreorder++ == 0) + fputs (".set\tnoreorder\n\t", file); + break; + + case ')': + if (set_noreorder == 0) + error ("internal error: %%) found without a %%( in assembler pattern"); + + else if (--set_noreorder == 0) + fputs ("\n\t.set\treorder", file); + + break; + + case '[': + if (set_noat++ == 0) + fputs (".set\tnoat\n\t", file); + break; + + case ']': + if (set_noat == 0) + error ("internal error: %%] found without a %%[ in assembler pattern"); + + else if (--set_noat == 0) + fputs ("\n\t.set\tat", file); + + break; + + case '<': + if (set_nomacro++ == 0) + fputs (".set\tnomacro\n\t", file); + break; + + case '>': + if (set_nomacro == 0) + error ("internal error: %%> found without a %%< in assembler pattern"); + + else if (--set_nomacro == 0) + fputs ("\n\t.set\tmacro", file); + + break; + + case '{': + if (set_volatile++ == 0) + fprintf (file, "%s.set\tvolatile\n\t", (TARGET_MIPS_AS) ? "" : "#"); + break; + + case '}': + if (set_volatile == 0) + error ("internal error: %%} found without a %%{ in assembler pattern"); + + else if (--set_volatile == 0) + fprintf (file, "\n\t%s.set\tnovolatile", (TARGET_MIPS_AS) ? "" : "#"); + + break; + } + return; + } + + if (! op) + { + error ("PRINT_OPERAND null pointer"); + return; + } + + code = GET_CODE (op); + if (letter == 'C') + switch (code) + { + case EQ: fputs ("eq", file); break; + case NE: fputs ("ne", file); break; + case GT: fputs ("gt", file); break; + case GE: fputs ("ge", file); break; + case LT: fputs ("lt", file); break; + case LE: fputs ("le", file); break; + case GTU: fputs ("gtu", file); break; + case GEU: fputs ("geu", file); break; + case LTU: fputs ("ltu", file); break; + case LEU: fputs ("leu", file); break; + + default: + abort_with_insn (op, "PRINT_OPERAND, illegal insn for %%C"); + } + + else if (letter == 'N') + switch (code) + { + case EQ: fputs ("ne", file); break; + case NE: fputs ("eq", file); break; + case GT: fputs ("le", file); break; + case GE: fputs ("lt", file); break; + case LT: fputs ("ge", file); break; + case LE: fputs ("gt", file); break; + case GTU: fputs ("leu", file); break; + case GEU: fputs ("ltu", file); break; + case LTU: fputs ("geu", file); break; + case LEU: fputs ("gtu", file); break; + + default: + abort_with_insn (op, "PRINT_OPERAND, illegal insn for %%N"); + } + + else if (code == REG) + { + register int regnum = REGNO (op); + + if (letter == 'M') + regnum += MOST_SIGNIFICANT_WORD; + + else if (letter == 'L') + regnum += LEAST_SIGNIFICANT_WORD; + + else if (letter == 'D') + regnum++; + + fprintf (file, "%s", reg_names[regnum]); + } + + else if (code == MEM) + output_address (XEXP (op, 0)); + + else if (code == CONST_DOUBLE) + { +#if HOST_FLOAT_FORMAT == TARGET_FLOAT_FORMAT + union { double d; int i[2]; } u; + u.i[0] = CONST_DOUBLE_LOW (op); + u.i[1] = CONST_DOUBLE_HIGH (op); + if (GET_MODE (op) == SFmode) + { + float f; + f = u.d; + u.d = f; + } + fprintf (file, "%.20e", u.d); +#else + fatal ("CONST_DOUBLE found in cross compilation"); +#endif + } + + else if ((letter == 'x') && (GET_CODE(op) == CONST_INT)) + fprintf (file, "0x%04x", 0xffff & (INTVAL(op))); + + else if ((letter == 'X') && (GET_CODE(op) == CONST_INT)) + fprintf (file, "0x%08x", INTVAL(op)); + + else if ((letter == 'd') && (GET_CODE(op) == CONST_INT)) + fprintf (file, "%d", (INTVAL(op))); + + else if (letter == 'z' + && (GET_CODE (op) == CONST_INT) + && INTVAL (op) == 0) + fputs (reg_names[GP_REG_FIRST], file); + + else if (letter == 'd' || letter == 'x' || letter == 'X') + fatal ("PRINT_OPERAND: letter %c was found & insn was not CONST_INT", letter); + + else + output_addr_const (file, op); +} + + +/* A C compound statement to output to stdio stream STREAM the + assembler syntax for an instruction operand that is a memory + reference whose address is ADDR. ADDR is an RTL expression. + + On some machines, the syntax for a symbolic address depends on + the section that the address refers to. On these machines, + define the macro `ENCODE_SECTION_INFO' to store the information + into the `symbol_ref', and then check for it here. */ + +void +print_operand_address (file, addr) + FILE *file; + rtx addr; +{ + if (!addr) + error ("PRINT_OPERAND_ADDRESS, null pointer"); + + else + switch (GET_CODE (addr)) + { + default: + abort_with_insn (addr, "PRINT_OPERAND_ADDRESS, illegal insn #1"); + break; + + case REG: + fprintf (file, "0(%s)", reg_names [REGNO (addr)]); + break; + + case PLUS: + { + register rtx reg = (rtx)0; + register rtx offset = (rtx)0; + register rtx arg0 = XEXP (addr, 0); + register rtx arg1 = XEXP (addr, 1); + + if (GET_CODE (arg0) == REG) + { + reg = arg0; + offset = arg1; + if (GET_CODE (offset) == REG) + abort_with_insn (addr, "PRINT_OPERAND_ADDRESS, 2 regs"); + } + else if (GET_CODE (arg1) == REG) + { + reg = arg1; + offset = arg0; + } + else if (CONSTANT_P (arg0) && CONSTANT_P (arg1)) + { + output_addr_const (file, addr); + break; + } + else + abort_with_insn (addr, "PRINT_OPERAND_ADDRESS, no regs"); + + if (!CONSTANT_P (offset)) + abort_with_insn (addr, "PRINT_OPERAND_ADDRESS, illegal insn #2"); + + output_addr_const (file, offset); + fprintf (file, "(%s)", reg_names [REGNO (reg)]); + } + break; + + case LABEL_REF: + case SYMBOL_REF: + case CONST_INT: + case CONST: + output_addr_const (file, addr); + break; + } +} + + +/* If optimizing for the global pointer, keep track of all of + the externs, so that at the end of the file, we can emit + the appropriate .extern declaration for them, before writing + out the text section. We assume that all names passed to + us are in the permanent obstack, so that they will be valid + at the end of the compilation. + + If we have -G 0, or the extern size is unknown, don't bother + emitting the .externs. */ + +int +mips_output_external (file, decl, name) + FILE *file; + tree decl; + char *name; +{ + register struct extern_list *p; + int len; + + if (TARGET_GP_OPT + && mips_section_threshold != 0 + && ((TREE_CODE (decl)) != FUNCTION_DECL) + && ((len = int_size_in_bytes (TREE_TYPE (decl))) > 0)) + { + p = (struct extern_list *)permalloc ((long) sizeof (struct extern_list)); + p->next = extern_head; + p->name = name; + p->size = len; + extern_head = p; + } + return 0; +} + + +/* Compute a string to use as a temporary file name. */ + +static FILE * +make_temp_file () +{ + char *temp_filename; + FILE *stream; + char *base = getenv ("TMPDIR"); + int len; + + if (base == (char *)0) + { +#ifdef P_tmpdir + if (access (P_tmpdir, R_OK | W_OK) == 0) + base = P_tmpdir; + else +#endif + if (access ("/usr/tmp", R_OK | W_OK) == 0) + base = "/usr/tmp/"; + else + base = "/tmp/"; + } + + len = strlen (base); + temp_filename = (char *) alloca (len + sizeof("/ccXXXXXX")); + strcpy (temp_filename, base); + if (len > 0 && temp_filename[len-1] != '/') + temp_filename[len++] = '/'; + + strcpy (temp_filename + len, "ccXXXXXX"); + mktemp (temp_filename); + + stream = fopen (temp_filename, "w+"); + if (!stream) + pfatal_with_name (temp_filename); + + unlink (temp_filename); + return stream; +} + + +/* Emit a new filename to a stream. If this is MIPS ECOFF, watch out + for .file's that start within a function. If we are smuggling stabs, try to + put out a MIPS ECOFF file and a stab. */ + +void +mips_output_filename (stream, name) + FILE *stream; + char *name; +{ + static int first_time = TRUE; + char ltext_label_name[100]; + + if (first_time) + { + first_time = FALSE; + SET_FILE_NUMBER (); + current_function_file = name; + fprintf (stream, "\t.file\t%d \"%s\"\n", num_source_filenames, name); + } + + else if (!TARGET_GAS && write_symbols == DBX_DEBUG) + { + ASM_GENERATE_INTERNAL_LABEL (ltext_label_name, "Ltext", 0); + fprintf (stream, "%s \"%s\",%d,0,0,%s\n", ASM_STABS_OP, + name, N_SOL, <ext_label_name[1]); + } + + else if (name != current_function_file + && strcmp (name, current_function_file) != 0) + { + if (inside_function && !TARGET_GAS) + { + if (!file_in_function_warning) + { + file_in_function_warning = TRUE; + ignore_line_number = TRUE; + warning ("MIPS ECOFF format does not allow changing filenames within functions with #line"); + } + + fprintf (stream, "\t#.file\t%d \"%s\"\n", num_source_filenames, name); + } + + else + { + SET_FILE_NUMBER (); + current_function_file = name; + fprintf (stream, "\t.file\t%d \"%s\"\n", num_source_filenames, name); + } + } +} + + +/* Emit a linenumber. For encapsulated stabs, we need to put out a stab + as well as a .loc, since it is possible that MIPS ECOFF might not be + able to represent the location for inlines that come from a different + file. */ + +void +mips_output_lineno (stream, line) + FILE *stream; + int line; +{ + if (!TARGET_GAS && write_symbols == DBX_DEBUG) + { + ++sym_lineno; + fprintf (stream, "$LM%d:\n\t%s %d,0,%d,$LM%d\n", + sym_lineno, ASM_STABN_OP, N_SLINE, line, sym_lineno); + } + + else + { + fprintf (stream, "\n\t%s.loc\t%d %d\n", + (ignore_line_number) ? "#" : "", + num_source_filenames, line); + + LABEL_AFTER_LOC (stream); + } +} + + +/* Output at beginning of assembler file. + If we are optimizing to use the global pointer, create a temporary + file to hold all of the text stuff, and write it out to the end. + This is needed because the MIPS assembler is evidently one pass, + and if it hasn't seen the relevant .comm/.lcomm/.extern/.sdata + declaration when the code is processed, it generates a two + instruction sequence. */ + +void +mips_asm_file_start (stream) + FILE *stream; +{ + ASM_OUTPUT_SOURCE_FILENAME (stream, main_input_filename); + + /* Versions of the MIPS assembler before 2.20 generate errors + if a branch inside of a .set noreorder section jumps to a + label outside of the .set noreorder section. Revision 2.20 + just set nobopt silently rather than fixing the bug. */ + + if (TARGET_MIPS_AS && optimize && flag_delayed_branch) + fprintf (stream, "\t.set\tnobopt\n"); + + /* Generate the pseudo ops that the Pyramid based System V.4 wants. */ + if (TARGET_ABICALLS) + fprintf (stream, "\t.abicalls\n"); + + /* put gcc_compiled. in data, not text */ + data_section (); + + if (TARGET_GP_OPT) + { + asm_out_data_file = stream; + asm_out_text_file = make_temp_file (); + } + else + asm_out_data_file = asm_out_text_file = stream; + + if (TARGET_NAME_REGS) + fprintf (asm_out_file, "#include \n"); + + print_options (stream); +} + + +/* If we are optimizing the global pointer, emit the text section now + and any small externs which did not have .comm, etc that are + needed. Also, give a warning if the data area is more than 32K and + -pic because 3 instructions are needed to reference the data + pointers. */ + +void +mips_asm_file_end (file) + FILE *file; +{ + char buffer[8192]; + tree name_tree; + struct extern_list *p; + int len; + + if (TARGET_GP_OPT) + { + if (extern_head) + fputs ("\n", file); + + for (p = extern_head; p != 0; p = p->next) + { + name_tree = get_identifier (p->name); + if (!TREE_ADDRESSABLE (name_tree)) + { + TREE_ADDRESSABLE (name_tree) = 1; + fputs ("\t.extern\t", file); + assemble_name (file, p->name); + fprintf (file, ", %d\n", p->size); + } + } + + fprintf (file, "\n\t.text\n"); + rewind (asm_out_text_file); + if (ferror (asm_out_text_file)) + fatal_io_error ("write of text assembly file in mips_asm_file_end"); + + while ((len = fread (buffer, 1, sizeof (buffer), asm_out_text_file)) > 0) + if (fwrite (buffer, 1, len, file) != len) + pfatal_with_name ("write of final assembly file in mips_asm_file_end"); + + if (len < 0) + pfatal_with_name ("read of text assembly file in mips_asm_file_end"); + + if (fclose (asm_out_text_file) != 0) + pfatal_with_name ("close of tempfile in mips_asm_file_end"); + } +} + + +/* Return the bytes needed to compute the frame pointer from the current + stack pointer. + + Mips stack frames look like: + + Before call After call + +-----------------------+ +-----------------------+ + high | | | | + mem. | | | | + | caller's temps. | | caller's temps. | + | | | | + +-----------------------+ +-----------------------+ + | | | | + | arguments on stack. | | arguments on stack. | + | | | | + +-----------------------+ +-----------------------+ + | 4 words to save | | 4 words to save | + | arguments passed | | arguments passed | + | in registers, even | | in registers, even | + SP->| if not passed. | FP->| if not passed. | + +-----------------------+ +-----------------------+ + | | + | GP save for V.4 abi | + | | + +-----------------------+ + | | + | local variables | + | | + +-----------------------+ + | | + | fp register save | + | | + +-----------------------+ + | | + | gp register save | + | | + +-----------------------+ + | | + | alloca allocations | + | | + +-----------------------+ + | | + | arguments on stack | + | | + +-----------------------+ + | 4 words to save | + | arguments passed | + | in registers, even | + low SP->| if not passed. | + memory +-----------------------+ + +*/ + +unsigned long +compute_frame_size (size) + int size; /* # of var. bytes allocated */ +{ + int regno; + unsigned long total_size; /* # bytes that the entire frame takes up */ + unsigned long var_size; /* # bytes that variables take up */ + unsigned long args_size; /* # bytes that outgoing arguments take up */ + unsigned long extra_size; /* # extra bytes */ + unsigned int gp_reg_rounded; /* # bytes needed to store gp after rounding */ + unsigned int gp_reg_size; /* # bytes needed to store gp regs */ + unsigned int fp_reg_size; /* # bytes needed to store fp regs */ + unsigned long mask; /* mask of saved gp registers */ + unsigned long fmask; /* mask of saved fp registers */ + int fp_inc; /* 1 or 2 depending on the size of fp regs */ + int fp_bits; /* bitmask to use for each fp register */ + + extra_size = MIPS_STACK_ALIGN (((TARGET_ABICALLS) ? UNITS_PER_WORD : 0) + -STARTING_FRAME_OFFSET); + + var_size = MIPS_STACK_ALIGN (size); + args_size = MIPS_STACK_ALIGN (current_function_outgoing_args_size); + total_size = var_size + args_size + extra_size; + gp_reg_size = 0; + fp_reg_size = 0; + mask = 0; + fmask = 0; + + /* Calculate space needed for gp registers. */ + for (regno = GP_REG_FIRST; regno <= GP_REG_LAST; regno++) + { + if (MUST_SAVE_REGISTER (regno)) + { + gp_reg_size += UNITS_PER_WORD; + mask |= 1 << (regno - GP_REG_FIRST); + } + } + + /* Calculate space needed for fp registers. */ + if (TARGET_FLOAT64) + { + fp_inc = 1; + fp_bits = 1; + } + else + { + fp_inc = 2; + fp_bits = 3; + } + + for (regno = FP_REG_FIRST; regno <= FP_REG_LAST; regno += fp_inc) + { + if (regs_ever_live[regno] && !call_used_regs[regno]) + { + fp_reg_size += 2*UNITS_PER_WORD; + fmask |= fp_bits << (regno - FP_REG_FIRST); + } + } + + gp_reg_rounded = MIPS_STACK_ALIGN (gp_reg_size); + total_size += gp_reg_rounded + fp_reg_size; + + if (total_size == extra_size) + total_size = extra_size = 0; + + /* Save other computed information. */ + current_frame_info.total_size = total_size; + current_frame_info.var_size = var_size; + current_frame_info.args_size = args_size; + current_frame_info.extra_size = extra_size; + current_frame_info.gp_reg_size = gp_reg_size; + current_frame_info.fp_reg_size = fp_reg_size; + current_frame_info.mask = mask; + current_frame_info.fmask = fmask; + current_frame_info.initialized = reload_completed; + + if (mask) + { + unsigned long offset = args_size + gp_reg_size - UNITS_PER_WORD; + current_frame_info.gp_sp_offset = offset; + current_frame_info.gp_save_offset = offset - total_size; + } + + if (fmask) + { + unsigned long offset = args_size + gp_reg_rounded + fp_reg_size - 2*UNITS_PER_WORD; + current_frame_info.fp_sp_offset = offset; + current_frame_info.fp_save_offset = offset - total_size + UNITS_PER_WORD; + } + + /* Ok, we're done. */ + return total_size; +} + + +/* Common code to save/restore registers. */ + +void +save_restore (file, gp_op, gp_2word_op, fp_op) + FILE *file; /* stream to write to */ + char *gp_op; /* operation to do on gp registers */ + char *gp_2word_op; /* 2 word op to do on gp registers */ + char *fp_op; /* operation to do on fp registers */ +{ + int regno; + unsigned long mask = current_frame_info.mask; + unsigned long fmask = current_frame_info.fmask; + unsigned long gp_offset; + unsigned long fp_offset; + unsigned long max_offset; + char *base_reg; + + if (mask == 0 && fmask == 0) + return; + + base_reg = reg_names[STACK_POINTER_REGNUM]; + gp_offset = current_frame_info.gp_sp_offset; + fp_offset = current_frame_info.fp_sp_offset; + max_offset = (gp_offset > fp_offset) ? gp_offset : fp_offset; + + /* Deal with calling functions with a large structure. */ + if (max_offset >= 32768) + { + char *temp = reg_names[MIPS_TEMP2_REGNUM]; + fprintf (file, "\tli\t%s,%ld\n", temp, max_offset); + fprintf (file, "\taddu\t%s,%s,%s\n", temp, temp, base_reg); + base_reg = temp; + gp_offset = max_offset - gp_offset; + fp_offset = max_offset - fp_offset; + } + + /* Save registers starting from high to low. The debuggers prefer + at least the return register be stored at func+4, and also it + allows us not to need a nop in the epilog if at least one + register is reloaded in addition to return address. */ + + if (mask || frame_pointer_needed) + { + for (regno = GP_REG_LAST; regno >= GP_REG_FIRST; regno--) + { + if ((mask & (1L << (regno - GP_REG_FIRST))) != 0 + || (regno == FRAME_POINTER_REGNUM && frame_pointer_needed)) + { + fprintf (file, "\t%s\t%s,%d(%s)\n", + gp_op, reg_names[regno], + gp_offset, base_reg); + + gp_offset -= UNITS_PER_WORD; + } + } + } + + if (fmask) + { + int fp_inc = (TARGET_FLOAT64) ? 1 : 2; + + for (regno = FP_REG_LAST-1; regno >= FP_REG_FIRST; regno -= fp_inc) + { + if ((fmask & (1L << (regno - FP_REG_FIRST))) != 0) + { + fprintf (file, "\t%s\t%s,%d(%s)\n", + fp_op, reg_names[regno], fp_offset, base_reg); + + fp_offset -= 2*UNITS_PER_WORD; + } + } + } +} + + +/* Set up the stack and frame (if desired) for the function. */ + +void +function_prologue (file, size) + FILE *file; + int size; +{ + int regno; + int tsize; + char *sp_str = reg_names[STACK_POINTER_REGNUM]; + char *fp_str = (!frame_pointer_needed) + ? sp_str + : reg_names[FRAME_POINTER_REGNUM]; + tree fndecl = current_function_decl; /* current... is tooo long */ + tree fntype = TREE_TYPE (fndecl); + tree fnargs = (TREE_CODE (fntype) != METHOD_TYPE) + ? DECL_ARGUMENTS (fndecl) + : 0; + tree next_arg; + tree cur_arg; + char *arg_name = (char *)0; + CUMULATIVE_ARGS args_so_far; + + ASM_OUTPUT_SOURCE_FILENAME (file, DECL_SOURCE_FILE (current_function_decl)); + ASM_OUTPUT_SOURCE_LINE (file, DECL_SOURCE_LINE (current_function_decl)); + + inside_function = 1; + fputs ("\t.ent\t", file); + assemble_name (file, current_function_name); + fputs ("\n", file); + assemble_name (file, current_function_name); + fputs (":\n", file); + + if (TARGET_ABICALLS) + fprintf (file, + "\t.set\tnoreorder\n\t.cpload\t%s\n\t.set\treorder\n", + reg_names[ GP_REG_FIRST + 25 ]); + + /* Determine the last argument, and get its name. */ + for (cur_arg = fnargs; cur_arg != (tree)0; cur_arg = next_arg) + { + next_arg = TREE_CHAIN (cur_arg); + if (next_arg == (tree)0) + { + if (DECL_NAME (cur_arg)) + arg_name = IDENTIFIER_POINTER (DECL_NAME (cur_arg)); + + break; + } + } + + /* If this function is a varargs function, store any registers that + would normally hold arguments ($4 - $7) on the stack. */ + if ((TYPE_ARG_TYPES (fntype) != 0 + && (TREE_VALUE (tree_last (TYPE_ARG_TYPES (fntype))) != void_type_node)) + || (arg_name + && (strcmp (arg_name, "__builtin_va_alist") == 0 + || strcmp (arg_name, "va_alist") == 0))) + { + tree parm; + + regno = GP_ARG_FIRST; + INIT_CUMULATIVE_ARGS (args_so_far, fntype, (rtx)0); + + for (parm = fnargs; (parm && (regno <= GP_ARG_LAST)); parm = TREE_CHAIN (parm)) + { + rtx entry_parm; + enum machine_mode passed_mode; + tree type; + + type = DECL_ARG_TYPE (parm); + passed_mode = TYPE_MODE (type); + entry_parm = FUNCTION_ARG (args_so_far, passed_mode, + DECL_ARG_TYPE (parm), 1); + + if (entry_parm) + { + int words; + + /* passed in a register, so will get homed automatically */ + if (GET_MODE (entry_parm) == BLKmode) + words = (int_size_in_bytes (type) + 3) / 4; + else + words = (GET_MODE_SIZE (GET_MODE (entry_parm)) + 3) / 4; + + regno = REGNO (entry_parm) + words - 1; + } + else + { + regno = GP_ARG_LAST+1; + break; + } + + FUNCTION_ARG_ADVANCE (args_so_far, passed_mode, + DECL_ARG_TYPE (parm), 1); + } + + if (regno <= GP_ARG_LAST && (regno & 1) != 0) + { + fprintf (file, "\tsw\t%s,%d(%s)\t\t# varargs home register\n", + reg_names[regno], (regno - 4) * 4, sp_str); + regno++; + } + + for (; regno <= GP_ARG_LAST; regno += 2) + { + fprintf (file, "\tsd\t%s,%d(%s)\t\t# varargs home register\n", + reg_names[regno], (regno - 4) * 4, sp_str); + } + } + + size = MIPS_STACK_ALIGN (size); + tsize = (!current_frame_info.initialized) + ? compute_frame_size (size) + : current_frame_info.total_size; + + if (tsize > 0) + { + if (tsize <= 32767) + fprintf (file, + "\tsubu\t%s,%s,%d\t\t# vars= %d, regs= %d/%d, args = %d, extra= %d\n", + sp_str, sp_str, tsize, current_frame_info.var_size, + current_frame_info.gp_reg_size / 4, + current_frame_info.fp_reg_size / 8, + current_function_outgoing_args_size, + current_frame_info.extra_size); + else + fprintf (file, + "\tli\t%s,%d\n\tsubu\t%s,%s,%s\t\t# vars= %d, regs= %d/%d, args = %d, sfo= %d\n", + reg_names[MIPS_TEMP1_REGNUM], tsize, sp_str, sp_str, + reg_names[MIPS_TEMP1_REGNUM], current_frame_info.var_size, + current_frame_info.gp_reg_size / 4, + current_frame_info.fp_reg_size / 8, + current_function_outgoing_args_size, + current_frame_info.extra_size); + } + + if (TARGET_ABICALLS) + fprintf (file, "\t.cprestore %d\n", tsize + STARTING_FRAME_OFFSET); + + fprintf (file, "\t.frame\t%s,%d,%s\n\t.mask\t0x%08lx,%d\n\t.fmask\t0x%08lx,%d\n", + fp_str, + (frame_pointer_needed) ? 0 : tsize, + reg_names[31 + GP_REG_FIRST], + current_frame_info.mask, + current_frame_info.gp_save_offset, + current_frame_info.fmask, + current_frame_info.fp_save_offset); + + save_restore (file, "sw", "sd", "s.d"); + + if (frame_pointer_needed) + { + if (tsize <= 32767) + fprintf (file, "\taddu\t%s,%s,%d\t# set up frame pointer\n", fp_str, sp_str, tsize); + + else + fprintf (file, "\taddu\t%s,%s,%s\t# set up frame pointer\n", fp_str, sp_str, + reg_names[MIPS_TEMP1_REGNUM]); + } +} + + +/* Do any necessary cleanup after a function to restore stack, frame, and regs. */ + +void +function_epilogue (file, size) + FILE *file; + int size; +{ + int tsize; + char *sp_str = reg_names[STACK_POINTER_REGNUM]; + char *t1_str = reg_names[MIPS_TEMP1_REGNUM]; + rtx epilogue_delay = current_function_epilogue_delay_list; + int noreorder = !TARGET_MIPS_AS || (epilogue_delay != 0); + int noepilogue = FALSE; + int load_nop = FALSE; + int load_only_r31; + + /* The epilogue does not depend on any registers, but the stack + registers, so we assume that if we have 1 pending nop, it can be + ignored, and 2 it must be filled (2 nops occur for integer + multiply and divide). */ + + if (dslots_number_nops > 0) + { + if (dslots_number_nops == 1) + { + dslots_number_nops = 0; + dslots_load_filled++; + } + else + { + while (--dslots_number_nops > 0) + fputs ((set_noreorder) ? "\tnop\n" : "\t#nop\n", asm_out_file); + } + + if (set_noreorder > 0 && --set_noreorder == 0) + fputs ("\t.set\treorder\n", file); + } + + if (set_noat != 0) + { + set_noat = 0; + fputs ("\t.set\tat\n", file); + error ("internal gcc error: .set noat left on in epilogue"); + } + + if (set_nomacro != 0) + { + set_nomacro = 0; + fputs ("\t.set\tmacro\n", file); + error ("internal gcc error: .set nomacro left on in epilogue"); + } + + if (set_noreorder != 0) + { + set_noreorder = 0; + fputs ("\t.set\treorder\n", file); + error ("internal gcc error: .set noreorder left on in epilogue"); + } + + if (set_volatile != 0) + { + set_volatile = 0; + fprintf (file, "\t#.set\tnovolatile\n", (TARGET_MIPS_AS) ? "" : "#"); + error ("internal gcc error: .set volatile left on in epilogue"); + } + + size = MIPS_STACK_ALIGN (size); + tsize = (!current_frame_info.initialized) + ? compute_frame_size (size) + : current_frame_info.total_size; + + if (tsize == 0 && epilogue_delay == 0) + { + rtx insn = get_last_insn (); + + /* If the last insn was a BARRIER, we don't have to write any code + because a jump (aka return) was put there. */ + if (GET_CODE (insn) == NOTE) + insn = prev_nonnote_insn (insn); + if (insn && GET_CODE (insn) == BARRIER) + noepilogue = TRUE; + + noreorder = FALSE; + } + + if (!noepilogue) + { + /* In the reload sequence, we don't need to fill the load delay + slots for most of the loads, also see if we can fill the final + delay slot if not otherwise filled by the reload sequence. */ + + if (noreorder) + fprintf (file, "\t.set\tnoreorder\n"); + + if (tsize > 32767) + fprintf (file, "\tli\t%s,%d\n", t1_str, tsize); + + if (frame_pointer_needed) + { + char *fp_str = reg_names[FRAME_POINTER_REGNUM]; + if (tsize > 32767) + fprintf (file,"\tsubu\t%s,%s,%s\t\t# sp not trusted here\n", + sp_str, fp_str, t1_str); + else + fprintf (file,"\tsubu\t%s,%s,%d\t\t# sp not trusted here\n", + sp_str, fp_str, tsize); + } + + save_restore (file, "lw", "ld", "l.d"); + + load_only_r31 = (current_frame_info.mask == (1 << 31) + && current_frame_info.fmask == 0); + + if (noreorder) + { + /* If the only register saved is the return address, we need a + nop, unless we have an instruction to put into it. Otherwise + we don't since reloading multiple registers doesn't reference + the register being loaded. */ + + if (load_only_r31) + { + if (epilogue_delay) + final_scan_insn (XEXP (epilogue_delay, 0), + file, + 1, /* optimize */ + -2, /* prescan */ + 1); /* nopeepholes */ + else + { + fprintf (file, "\tnop\n"); + load_nop = TRUE; + } + } + + fprintf (file, "\tj\t%s\n", reg_names[GP_REG_FIRST + 31]); + + if (tsize > 32767) + fprintf (file, "\taddu\t%s,%s,%s\n", sp_str, sp_str, t1_str); + + else if (tsize > 0) + fprintf (file, "\taddu\t%s,%s,%d\n", sp_str, sp_str, tsize); + + else if (!load_only_r31 && epilogue_delay != 0) + final_scan_insn (XEXP (epilogue_delay, 0), + file, + 1, /* optimize */ + -2, /* prescan */ + 1); /* nopeepholes */ + + fprintf (file, "\t.set\treorder\n"); + } + + else + { + if (tsize > 32767) + fprintf (file, "\taddu\t%s,%s,%s\n", sp_str, sp_str, t1_str); + + else if (tsize > 0) + fprintf (file, "\taddu\t%s,%s,%d\n", sp_str, sp_str, tsize); + + fprintf (file, "\tj\t%s\n", reg_names[GP_REG_FIRST + 31]); + } + } + + fputs ("\t.end\t", file); + assemble_name (file, current_function_name); + fputs ("\n", file); + + if (TARGET_STATS) + { + int num_gp_regs = current_frame_info.gp_reg_size / 4; + int num_fp_regs = current_frame_info.fp_reg_size / 8; + int num_regs = num_gp_regs + num_fp_regs; + + dslots_load_total += num_regs; + + if (!noepilogue) + dslots_jump_total++; + + if (noreorder) + { + dslots_load_filled += num_regs; + + /* If the only register saved is the return register, we + can't fill this register's delay slot. */ + + if (load_only_r31 && epilogue_delay == 0) + dslots_load_filled--; + + if (tsize > 0 || (!load_only_r31 && epilogue_delay != 0)) + dslots_jump_filled++; + } + + fprintf (stderr, + "%-20s fp=%c leaf=%c alloca=%c setjmp=%c stack=%4ld arg=%3ld reg=%2d/%d delay=%3d/%3dL %3d/%3dJ refs=%3d/%3d/%3d\n", + current_function_name, + (frame_pointer_needed) ? 'y' : 'n', + ((current_frame_info.mask & (1 << 31)) != 0) ? 'n' : 'y', + (current_function_calls_alloca) ? 'y' : 'n', + (current_function_calls_setjmp) ? 'y' : 'n', + (long)current_frame_info.total_size, + (long)current_function_outgoing_args_size, + num_gp_regs, num_fp_regs, + dslots_load_total, dslots_load_filled, + dslots_jump_total, dslots_jump_filled, + num_refs[0], num_refs[1], num_refs[2]); + } + + /* Reset state info for each function. */ + inside_function = FALSE; + ignore_line_number = FALSE; + dslots_load_total = 0; + dslots_jump_total = 0; + dslots_load_filled = 0; + dslots_jump_filled = 0; + num_refs[0] = 0; + num_refs[1] = 0; + num_refs[2] = 0; + mips_load_reg = (rtx)0; + mips_load_reg2 = (rtx)0; + number_functions_processed++; + current_frame_info = zero_frame_info; + + /* Restore the output file if optimizing the GP (optimizing the GP causes + the text to be diverted to a tempfile, so that data decls come before + references to the data). */ + + if (TARGET_GP_OPT) + asm_out_file = asm_out_data_file; +} + + +/* Define the number of delay slots needed for the function epilogue. + + On the mips, we need a slot if either no stack has been allocated, + or the only register saved is the return register. */ + +int +mips_epilogue_delay_slots () +{ + if (!current_frame_info.initialized) + (void) compute_frame_size (get_frame_size ()); + + if (current_frame_info.total_size == 0) + return 1; + + if (current_frame_info.mask == (1 << 31) && current_frame_info.fmask == 0) + return 1; + + return 0; +} + + +/* Return true if this function is known to have a null epilogue. + This allows the optimizer to omit jumps to jumps if no stack + was created. */ + +int +null_epilogue () +{ + if (!reload_completed) + return 0; + + if (current_frame_info.initialized) + return current_frame_info.total_size == 0; + + return (compute_frame_size (get_frame_size ())) == 0; +} + + +/* Encode in a declaration whether or not it is half-pic. */ + +void +half_pic_encode_section_info (decl) + tree decl; +{ +} -- 2.30.2