diff options
Diffstat (limited to 'gcc/config/i960/i960.c')
-rw-r--r-- | gcc/config/i960/i960.c | 2917 |
1 files changed, 2917 insertions, 0 deletions
diff --git a/gcc/config/i960/i960.c b/gcc/config/i960/i960.c new file mode 100644 index 00000000000..3d976b65fd4 --- /dev/null +++ b/gcc/config/i960/i960.c @@ -0,0 +1,2917 @@ +/* Subroutines used for code generation on intel 80960. + Copyright (C) 1992, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003 + Free Software Foundation, Inc. + Contributed by Steven McGeady, Intel Corp. + Additional Work by Glenn Colon-Bonet, Jonathan Shapiro, Andy Wilson + Converted to GCC 2.0 by Jim Wilson and Michael Tiemann, Cygnus Support. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING. If not, write to +the Free Software Foundation, 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tm.h" +#include <math.h> +#include "rtl.h" +#include "regs.h" +#include "hard-reg-set.h" +#include "real.h" +#include "insn-config.h" +#include "conditions.h" +#include "output.h" +#include "insn-attr.h" +#include "flags.h" +#include "tree.h" +#include "expr.h" +#include "except.h" +#include "function.h" +#include "recog.h" +#include "toplev.h" +#include "tm_p.h" +#include "target.h" +#include "target-def.h" + +static void i960_output_function_prologue (FILE *, HOST_WIDE_INT); +static void i960_output_function_epilogue (FILE *, HOST_WIDE_INT); +static void i960_output_mi_thunk (FILE *, tree, HOST_WIDE_INT, + HOST_WIDE_INT, tree); +static bool i960_rtx_costs (rtx, int, int, int *); +static int i960_address_cost (rtx); +static tree i960_build_builtin_va_list (void); + +/* Save the operands last given to a compare for use when we + generate a scc or bcc insn. */ + +rtx i960_compare_op0, i960_compare_op1; + +/* Used to implement #pragma align/noalign. Initialized by OVERRIDE_OPTIONS + macro in i960.h. */ + +int i960_maxbitalignment; +int i960_last_maxbitalignment; + +/* Used to implement switching between MEM and ALU insn types, for better + C series performance. */ + +enum insn_types i960_last_insn_type; + +/* The leaf-procedure return register. Set only if this is a leaf routine. */ + +static int i960_leaf_ret_reg; + +/* True if replacing tail calls with jumps is OK. */ + +static int tail_call_ok; + +/* A string containing a list of insns to emit in the epilogue so as to + restore all registers saved by the prologue. Created by the prologue + code as it saves registers away. */ + +char epilogue_string[1000]; + +/* A unique number (per function) for return labels. */ + +static int ret_label = 0; + +/* This is true if FNDECL is either a varargs or a stdarg function. + This is used to help identify functions that use an argument block. */ + +#define VARARGS_STDARG_FUNCTION(FNDECL) \ +(TYPE_ARG_TYPES (TREE_TYPE (FNDECL)) != 0 \ + && (TREE_VALUE (tree_last (TYPE_ARG_TYPES (TREE_TYPE (FNDECL))))) \ + != void_type_node) + +/* Initialize the GCC target structure. */ +#undef TARGET_ASM_ALIGNED_SI_OP +#define TARGET_ASM_ALIGNED_SI_OP "\t.word\t" + +#undef TARGET_ASM_FUNCTION_PROLOGUE +#define TARGET_ASM_FUNCTION_PROLOGUE i960_output_function_prologue +#undef TARGET_ASM_FUNCTION_EPILOGUE +#define TARGET_ASM_FUNCTION_EPILOGUE i960_output_function_epilogue + +#undef TARGET_ASM_OUTPUT_MI_THUNK +#define TARGET_ASM_OUTPUT_MI_THUNK i960_output_mi_thunk +#undef TARGET_CAN_ASM_OUTPUT_MI_THUNK +#define TARGET_CAN_ASM_OUTPUT_MI_THUNK default_can_output_mi_thunk_no_vcall + +#undef TARGET_RTX_COSTS +#define TARGET_RTX_COSTS i960_rtx_costs +#undef TARGET_ADDRESS_COST +#define TARGET_ADDRESS_COST i960_address_cost + +#undef TARGET_BUILD_BUILTIN_VA_LIST +#define TARGET_BUILD_BUILTIN_VA_LIST i960_build_builtin_va_list + +struct gcc_target targetm = TARGET_INITIALIZER; + +/* Override conflicting target switch options. + Doesn't actually detect if more than one -mARCH option is given, but + does handle the case of two blatantly conflicting -mARCH options. + + Also initialize variables before compiling any files. */ + +void +i960_initialize () +{ + if (TARGET_K_SERIES && TARGET_C_SERIES) + { + warning ("conflicting architectures defined - using C series"); + target_flags &= ~TARGET_FLAG_K_SERIES; + } + if (TARGET_K_SERIES && TARGET_MC) + { + warning ("conflicting architectures defined - using K series"); + target_flags &= ~TARGET_FLAG_MC; + } + if (TARGET_C_SERIES && TARGET_MC) + { + warning ("conflicting architectures defined - using C series"); + target_flags &= ~TARGET_FLAG_MC; + } + if (TARGET_IC_COMPAT3_0) + { + flag_short_enums = 1; + flag_signed_char = 1; + target_flags |= TARGET_FLAG_CLEAN_LINKAGE; + if (TARGET_IC_COMPAT2_0) + { + warning ("iC2.0 and iC3.0 are incompatible - using iC3.0"); + target_flags &= ~TARGET_FLAG_IC_COMPAT2_0; + } + } + if (TARGET_IC_COMPAT2_0) + { + flag_signed_char = 1; + target_flags |= TARGET_FLAG_CLEAN_LINKAGE; + } + + if (TARGET_IC_COMPAT2_0) + { + i960_maxbitalignment = 8; + i960_last_maxbitalignment = 128; + } + else + { + i960_maxbitalignment = 128; + i960_last_maxbitalignment = 8; + } +} + +/* Return true if OP can be used as the source of an fp move insn. */ + +int +fpmove_src_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return (GET_CODE (op) == CONST_DOUBLE || general_operand (op, mode)); +} + +#if 0 +/* Return true if OP is a register or zero. */ + +int +reg_or_zero_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return register_operand (op, mode) || op == const0_rtx; +} +#endif + +/* Return truth value of whether OP can be used as an operands in a three + address arithmetic insn (such as add %o1,7,%l2) of mode MODE. */ + +int +arith_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return (register_operand (op, mode) || literal (op, mode)); +} + +/* Return truth value of whether OP can be used as an operands in a three + address logic insn, possibly complementing OP, of mode MODE. */ + +int +logic_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return (register_operand (op, mode) + || (GET_CODE (op) == CONST_INT + && INTVAL(op) >= -32 && INTVAL(op) < 32)); +} + +/* Return true if OP is a register or a valid floating point literal. */ + +int +fp_arith_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return (register_operand (op, mode) || fp_literal (op, mode)); +} + +/* Return true if OP is a register or a valid signed integer literal. */ + +int +signed_arith_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return (register_operand (op, mode) || signed_literal (op, mode)); +} + +/* Return truth value of whether OP is an integer which fits the + range constraining immediate operands in three-address insns. */ + +int +literal (op, mode) + rtx op; + enum machine_mode mode ATTRIBUTE_UNUSED; +{ + return ((GET_CODE (op) == CONST_INT) && INTVAL(op) >= 0 && INTVAL(op) < 32); +} + +/* Return true if OP is a float constant of 1. */ + +int +fp_literal_one (op, mode) + rtx op; + enum machine_mode mode; +{ + return (TARGET_NUMERICS && mode == GET_MODE (op) && op == CONST1_RTX (mode)); +} + +/* Return true if OP is a float constant of 0. */ + +int +fp_literal_zero (op, mode) + rtx op; + enum machine_mode mode; +{ + return (TARGET_NUMERICS && mode == GET_MODE (op) && op == CONST0_RTX (mode)); +} + +/* Return true if OP is a valid floating point literal. */ + +int +fp_literal(op, mode) + rtx op; + enum machine_mode mode; +{ + return fp_literal_zero (op, mode) || fp_literal_one (op, mode); +} + +/* Return true if OP is a valid signed immediate constant. */ + +int +signed_literal(op, mode) + rtx op; + enum machine_mode mode ATTRIBUTE_UNUSED; +{ + return ((GET_CODE (op) == CONST_INT) && INTVAL(op) > -32 && INTVAL(op) < 32); +} + +/* Return truth value of statement that OP is a symbolic memory + operand of mode MODE. */ + +int +symbolic_memory_operand (op, mode) + rtx op; + enum machine_mode mode ATTRIBUTE_UNUSED; +{ + if (GET_CODE (op) == SUBREG) + op = SUBREG_REG (op); + if (GET_CODE (op) != MEM) + return 0; + op = XEXP (op, 0); + return (GET_CODE (op) == SYMBOL_REF || GET_CODE (op) == CONST + || GET_CODE (op) == HIGH || GET_CODE (op) == LABEL_REF); +} + +/* Return truth value of whether OP is EQ or NE. */ + +int +eq_or_neq (op, mode) + rtx op; + enum machine_mode mode ATTRIBUTE_UNUSED; +{ + return (GET_CODE (op) == EQ || GET_CODE (op) == NE); +} + +/* OP is an integer register or a constant. */ + +int +arith32_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + if (register_operand (op, mode)) + return 1; + return (CONSTANT_P (op)); +} + +/* Return true if OP is an integer constant which is a power of 2. */ + +int +power2_operand (op,mode) + rtx op; + enum machine_mode mode ATTRIBUTE_UNUSED; +{ + if (GET_CODE (op) != CONST_INT) + return 0; + + return exact_log2 (INTVAL (op)) >= 0; +} + +/* Return true if OP is an integer constant which is the complement of a + power of 2. */ + +int +cmplpower2_operand (op, mode) + rtx op; + enum machine_mode mode ATTRIBUTE_UNUSED; +{ + if (GET_CODE (op) != CONST_INT) + return 0; + + return exact_log2 (~ INTVAL (op)) >= 0; +} + +/* If VAL has only one bit set, return the index of that bit. Otherwise + return -1. */ + +int +bitpos (val) + unsigned int val; +{ + register int i; + + for (i = 0; val != 0; i++, val >>= 1) + { + if (val & 1) + { + if (val != 1) + return -1; + return i; + } + } + return -1; +} + +/* Return nonzero if OP is a mask, i.e. all one bits are consecutive. + The return value indicates how many consecutive nonzero bits exist + if this is a mask. This is the same as the next function, except that + it does not indicate what the start and stop bit positions are. */ + +int +is_mask (val) + unsigned int val; +{ + register int start, end = 0, i; + + start = -1; + for (i = 0; val != 0; val >>= 1, i++) + { + if (val & 1) + { + if (start < 0) + start = i; + + end = i; + continue; + } + /* Still looking for the first bit. */ + if (start < 0) + continue; + + /* We've seen the start of a bit sequence, and now a zero. There + must be more one bits, otherwise we would have exited the loop. + Therefore, it is not a mask. */ + if (val) + return 0; + } + + /* The bit string has ones from START to END bit positions only. */ + return end - start + 1; +} + +/* If VAL is a mask, then return nonzero, with S set to the starting bit + position and E set to the ending bit position of the mask. The return + value indicates how many consecutive bits exist in the mask. This is + the same as the previous function, except that it also indicates the + start and end bit positions of the mask. */ + +int +bitstr (val, s, e) + unsigned int val; + int *s, *e; +{ + register int start, end, i; + + start = -1; + end = -1; + for (i = 0; val != 0; val >>= 1, i++) + { + if (val & 1) + { + if (start < 0) + start = i; + + end = i; + continue; + } + + /* Still looking for the first bit. */ + if (start < 0) + continue; + + /* We've seen the start of a bit sequence, and now a zero. There + must be more one bits, otherwise we would have exited the loop. + Therefor, it is not a mask. */ + if (val) + { + start = -1; + end = -1; + break; + } + } + + /* The bit string has ones from START to END bit positions only. */ + *s = start; + *e = end; + return ((start < 0) ? 0 : end - start + 1); +} + +/* Return the machine mode to use for a comparison. */ + +enum machine_mode +select_cc_mode (op, x) + RTX_CODE op; + rtx x ATTRIBUTE_UNUSED; +{ + if (op == GTU || op == LTU || op == GEU || op == LEU) + return CC_UNSmode; + return CCmode; +} + +/* X and Y are two things to compare using CODE. Emit the compare insn and + return the rtx for register 36 in the proper mode. */ + +rtx +gen_compare_reg (code, x, y) + enum rtx_code code; + rtx x, y; +{ + rtx cc_reg; + enum machine_mode ccmode = SELECT_CC_MODE (code, x, y); + enum machine_mode mode + = GET_MODE (x) == VOIDmode ? GET_MODE (y) : GET_MODE (x); + + if (mode == SImode) + { + if (! arith_operand (x, mode)) + x = force_reg (SImode, x); + if (! arith_operand (y, mode)) + y = force_reg (SImode, y); + } + + cc_reg = gen_rtx_REG (ccmode, 36); + emit_insn (gen_rtx_SET (VOIDmode, cc_reg, + gen_rtx_COMPARE (ccmode, x, y))); + + return cc_reg; +} + +/* For the i960, REG is cost 1, REG+immed CONST is cost 2, REG+REG is cost 2, + REG+nonimmed CONST is cost 4. REG+SYMBOL_REF, SYMBOL_REF, and similar + are 4. Indexed addresses are cost 6. */ + +/* ??? Try using just RTX_COST, i.e. not defining ADDRESS_COST. */ + +static int +i960_address_cost (x) + rtx x; +{ + if (GET_CODE (x) == REG) + return 1; + + /* This is a MEMA operand -- it's free. */ + if (GET_CODE (x) == CONST_INT + && INTVAL (x) >= 0 + && INTVAL (x) < 4096) + return 0; + + if (GET_CODE (x) == PLUS) + { + rtx base = XEXP (x, 0); + rtx offset = XEXP (x, 1); + + if (GET_CODE (base) == SUBREG) + base = SUBREG_REG (base); + if (GET_CODE (offset) == SUBREG) + offset = SUBREG_REG (offset); + + if (GET_CODE (base) == REG) + { + if (GET_CODE (offset) == REG) + return 2; + if (GET_CODE (offset) == CONST_INT) + { + if ((unsigned)INTVAL (offset) < 2047) + return 2; + return 4; + } + if (CONSTANT_P (offset)) + return 4; + } + if (GET_CODE (base) == PLUS || GET_CODE (base) == MULT) + return 6; + + /* This is an invalid address. The return value doesn't matter, but + for convenience we make this more expensive than anything else. */ + return 12; + } + if (GET_CODE (x) == MULT) + return 6; + + /* Symbol_refs and other unrecognized addresses are cost 4. */ + return 4; +} + +/* Emit insns to move operands[1] into operands[0]. + + Return 1 if we have written out everything that needs to be done to + do the move. Otherwise, return 0 and the caller will emit the move + normally. */ + +int +emit_move_sequence (operands, mode) + rtx *operands; + enum machine_mode mode; +{ + /* We can only store registers to memory. */ + + if (GET_CODE (operands[0]) == MEM && GET_CODE (operands[1]) != REG + && (operands[1] != const0_rtx || current_function_args_size + || current_function_stdarg + || rtx_equal_function_value_matters)) + /* Here we use the same test as movsi+1 pattern -- see i960.md. */ + operands[1] = force_reg (mode, operands[1]); + + /* Storing multi-word values in unaligned hard registers to memory may + require a scratch since we have to store them a register at a time and + adding 4 to the memory address may not yield a valid insn. */ + /* ??? We don't always need the scratch, but that would complicate things. + Maybe later. */ + /* ??? We must also handle stores to pseudos here, because the pseudo may be + replaced with a MEM later. This would be cleaner if we didn't have + a separate pattern for unaligned DImode/TImode stores. */ + if (GET_MODE_SIZE (mode) > UNITS_PER_WORD + && (GET_CODE (operands[0]) == MEM + || (GET_CODE (operands[0]) == REG + && REGNO (operands[0]) >= FIRST_PSEUDO_REGISTER)) + && GET_CODE (operands[1]) == REG + && REGNO (operands[1]) < FIRST_PSEUDO_REGISTER + && ! HARD_REGNO_MODE_OK (REGNO (operands[1]), mode)) + { + emit_insn (gen_rtx_PARALLEL + (VOIDmode, + gen_rtvec (2, + gen_rtx_SET (VOIDmode, operands[0], operands[1]), + gen_rtx_CLOBBER (VOIDmode, + gen_rtx_SCRATCH (Pmode))))); + return 1; + } + + return 0; +} + +/* Output assembler to move a double word value. */ + +const char * +i960_output_move_double (dst, src) + rtx dst, src; +{ + rtx operands[5]; + + if (GET_CODE (dst) == REG + && GET_CODE (src) == REG) + { + if ((REGNO (src) & 1) + || (REGNO (dst) & 1)) + { + /* We normally copy the low-numbered register first. However, if + the second source register is the same as the first destination + register, we must copy in the opposite order. */ + if (REGNO (src) + 1 == REGNO (dst)) + return "mov %D1,%D0\n\tmov %1,%0"; + else + return "mov %1,%0\n\tmov %D1,%D0"; + } + else + return "movl %1,%0"; + } + else if (GET_CODE (dst) == REG + && GET_CODE (src) == CONST_INT + && CONST_OK_FOR_LETTER_P (INTVAL (src), 'I')) + { + if (REGNO (dst) & 1) + return "mov %1,%0\n\tmov 0,%D0"; + else + return "movl %1,%0"; + } + else if (GET_CODE (dst) == REG + && GET_CODE (src) == MEM) + { + if (REGNO (dst) & 1) + { + /* One can optimize a few cases here, but you have to be + careful of clobbering registers used in the address and + edge conditions. */ + operands[0] = dst; + operands[1] = src; + operands[2] = gen_rtx_REG (Pmode, REGNO (dst) + 1); + operands[3] = gen_rtx_MEM (word_mode, operands[2]); + operands[4] = adjust_address (operands[3], word_mode, + UNITS_PER_WORD); + output_asm_insn + ("lda %1,%2\n\tld %3,%0\n\tld %4,%D0", operands); + return ""; + } + else + return "ldl %1,%0"; + } + else if (GET_CODE (dst) == MEM + && GET_CODE (src) == REG) + { + if (REGNO (src) & 1) + { + operands[0] = dst; + operands[1] = adjust_address (dst, word_mode, UNITS_PER_WORD); + if (! memory_address_p (word_mode, XEXP (operands[1], 0))) + abort (); + operands[2] = src; + output_asm_insn ("st %2,%0\n\tst %D2,%1", operands); + return ""; + } + return "stl %1,%0"; + } + else + abort (); +} + +/* Output assembler to move a double word zero. */ + +const char * +i960_output_move_double_zero (dst) + rtx dst; +{ + rtx operands[2]; + + operands[0] = dst; + { + operands[1] = adjust_address (dst, word_mode, 4); + output_asm_insn ("st g14,%0\n\tst g14,%1", operands); + } + return ""; +} + +/* Output assembler to move a quad word value. */ + +const char * +i960_output_move_quad (dst, src) + rtx dst, src; +{ + rtx operands[7]; + + if (GET_CODE (dst) == REG + && GET_CODE (src) == REG) + { + if ((REGNO (src) & 3) + || (REGNO (dst) & 3)) + { + /* We normally copy starting with the low numbered register. + However, if there is an overlap such that the first dest reg + is <= the last source reg but not < the first source reg, we + must copy in the opposite order. */ + if (REGNO (dst) <= REGNO (src) + 3 + && REGNO (dst) >= REGNO (src)) + return "mov %F1,%F0\n\tmov %E1,%E0\n\tmov %D1,%D0\n\tmov %1,%0"; + else + return "mov %1,%0\n\tmov %D1,%D0\n\tmov %E1,%E0\n\tmov %F1,%F0"; + } + else + return "movq %1,%0"; + } + else if (GET_CODE (dst) == REG + && GET_CODE (src) == CONST_INT + && CONST_OK_FOR_LETTER_P (INTVAL (src), 'I')) + { + if (REGNO (dst) & 3) + return "mov %1,%0\n\tmov 0,%D0\n\tmov 0,%E0\n\tmov 0,%F0"; + else + return "movq %1,%0"; + } + else if (GET_CODE (dst) == REG + && GET_CODE (src) == MEM) + { + if (REGNO (dst) & 3) + { + /* One can optimize a few cases here, but you have to be + careful of clobbering registers used in the address and + edge conditions. */ + operands[0] = dst; + operands[1] = src; + operands[2] = gen_rtx_REG (Pmode, REGNO (dst) + 3); + operands[3] = gen_rtx_MEM (word_mode, operands[2]); + operands[4] + = adjust_address (operands[3], word_mode, UNITS_PER_WORD); + operands[5] + = adjust_address (operands[4], word_mode, UNITS_PER_WORD); + operands[6] + = adjust_address (operands[5], word_mode, UNITS_PER_WORD); + output_asm_insn ("lda %1,%2\n\tld %3,%0\n\tld %4,%D0\n\tld %5,%E0\n\tld %6,%F0", operands); + return ""; + } + else + return "ldq %1,%0"; + } + else if (GET_CODE (dst) == MEM + && GET_CODE (src) == REG) + { + if (REGNO (src) & 3) + { + operands[0] = dst; + operands[1] = adjust_address (dst, word_mode, UNITS_PER_WORD); + operands[2] = adjust_address (dst, word_mode, 2 * UNITS_PER_WORD); + operands[3] = adjust_address (dst, word_mode, 3 * UNITS_PER_WORD); + if (! memory_address_p (word_mode, XEXP (operands[3], 0))) + abort (); + operands[4] = src; + output_asm_insn ("st %4,%0\n\tst %D4,%1\n\tst %E4,%2\n\tst %F4,%3", operands); + return ""; + } + return "stq %1,%0"; + } + else + abort (); +} + +/* Output assembler to move a quad word zero. */ + +const char * +i960_output_move_quad_zero (dst) + rtx dst; +{ + rtx operands[4]; + + operands[0] = dst; + { + operands[1] = adjust_address (dst, word_mode, 4); + operands[2] = adjust_address (dst, word_mode, 8); + operands[3] = adjust_address (dst, word_mode, 12); + output_asm_insn ("st g14,%0\n\tst g14,%1\n\tst g14,%2\n\tst g14,%3", operands); + } + return ""; +} + + +/* Emit insns to load a constant to non-floating point registers. + Uses several strategies to try to use as few insns as possible. */ + +const char * +i960_output_ldconst (dst, src) + register rtx dst, src; +{ + register int rsrc1; + register unsigned rsrc2; + enum machine_mode mode = GET_MODE (dst); + rtx operands[4]; + + operands[0] = operands[2] = dst; + operands[1] = operands[3] = src; + + /* Anything that isn't a compile time constant, such as a SYMBOL_REF, + must be a ldconst insn. */ + + if (GET_CODE (src) != CONST_INT && GET_CODE (src) != CONST_DOUBLE) + { + output_asm_insn ("ldconst %1,%0", operands); + return ""; + } + else if (mode == TFmode) + { + REAL_VALUE_TYPE d; + long value_long[3]; + int i; + + if (fp_literal_zero (src, TFmode)) + return "movt 0,%0"; + + REAL_VALUE_FROM_CONST_DOUBLE (d, src); + REAL_VALUE_TO_TARGET_LONG_DOUBLE (d, value_long); + + output_asm_insn ("# ldconst %1,%0",operands); + + for (i = 0; i < 3; i++) + { + operands[0] = gen_rtx_REG (SImode, REGNO (dst) + i); + operands[1] = GEN_INT (value_long[i]); + output_asm_insn (i960_output_ldconst (operands[0], operands[1]), + operands); + } + + return ""; + } + else if (mode == DFmode) + { + rtx first, second; + + if (fp_literal_zero (src, DFmode)) + return "movl 0,%0"; + + split_double (src, &first, &second); + + output_asm_insn ("# ldconst %1,%0",operands); + + operands[0] = gen_rtx_REG (SImode, REGNO (dst)); + operands[1] = first; + output_asm_insn (i960_output_ldconst (operands[0], operands[1]), + operands); + operands[0] = gen_rtx_REG (SImode, REGNO (dst) + 1); + operands[1] = second; + output_asm_insn (i960_output_ldconst (operands[0], operands[1]), + operands); + return ""; + } + else if (mode == SFmode) + { + REAL_VALUE_TYPE d; + long value; + + REAL_VALUE_FROM_CONST_DOUBLE (d, src); + REAL_VALUE_TO_TARGET_SINGLE (d, value); + + output_asm_insn ("# ldconst %1,%0",operands); + operands[0] = gen_rtx_REG (SImode, REGNO (dst)); + operands[1] = GEN_INT (value); + output_asm_insn (i960_output_ldconst (operands[0], operands[1]), + operands); + return ""; + } + else if (mode == TImode) + { + /* ??? This is currently not handled at all. */ + abort (); + + /* Note: lowest order word goes in lowest numbered reg. */ + rsrc1 = INTVAL (src); + if (rsrc1 >= 0 && rsrc1 < 32) + return "movq %1,%0"; + else + output_asm_insn ("movq\t0,%0\t# ldconstq %1,%0",operands); + /* Go pick up the low-order word. */ + } + else if (mode == DImode) + { + rtx upperhalf, lowerhalf, xoperands[2]; + + if (GET_CODE (src) == CONST_DOUBLE || GET_CODE (src) == CONST_INT) + split_double (src, &lowerhalf, &upperhalf); + + else + abort (); + + /* Note: lowest order word goes in lowest numbered reg. */ + /* Numbers from 0 to 31 can be handled with a single insn. */ + rsrc1 = INTVAL (lowerhalf); + if (upperhalf == const0_rtx && rsrc1 >= 0 && rsrc1 < 32) + return "movl %1,%0"; + + /* Output the upper half with a recursive call. */ + xoperands[0] = gen_rtx_REG (SImode, REGNO (dst) + 1); + xoperands[1] = upperhalf; + output_asm_insn (i960_output_ldconst (xoperands[0], xoperands[1]), + xoperands); + /* The lower word is emitted as normally. */ + } + else + { + rsrc1 = INTVAL (src); + if (mode == QImode) + { + if (rsrc1 > 0xff) + rsrc1 &= 0xff; + } + else if (mode == HImode) + { + if (rsrc1 > 0xffff) + rsrc1 &= 0xffff; + } + } + + if (rsrc1 >= 0) + { + /* ldconst 0..31,X -> mov 0..31,X */ + if (rsrc1 < 32) + { + if (i960_last_insn_type == I_TYPE_REG && TARGET_C_SERIES) + return "lda %1,%0"; + return "mov %1,%0"; + } + + /* ldconst 32..63,X -> add 31,nn,X */ + if (rsrc1 < 63) + { + if (i960_last_insn_type == I_TYPE_REG && TARGET_C_SERIES) + return "lda %1,%0"; + operands[1] = GEN_INT (rsrc1 - 31); + output_asm_insn ("addo\t31,%1,%0\t# ldconst %3,%0", operands); + return ""; + } + } + else if (rsrc1 < 0) + { + /* ldconst -1..-31 -> sub 0,0..31,X */ + if (rsrc1 >= -31) + { + /* return 'sub -(%1),0,%0' */ + operands[1] = GEN_INT (- rsrc1); + output_asm_insn ("subo\t%1,0,%0\t# ldconst %3,%0", operands); + return ""; + } + + /* ldconst -32 -> not 31,X */ + if (rsrc1 == -32) + { + operands[1] = GEN_INT (~rsrc1); + output_asm_insn ("not\t%1,%0 # ldconst %3,%0", operands); + return ""; + } + } + + /* If const is a single bit. */ + if (bitpos (rsrc1) >= 0) + { + operands[1] = GEN_INT (bitpos (rsrc1)); + output_asm_insn ("setbit\t%1,0,%0\t# ldconst %3,%0", operands); + return ""; + } + + /* If const is a bit string of less than 6 bits (1..31 shifted). */ + if (is_mask (rsrc1)) + { + int s, e; + + if (bitstr (rsrc1, &s, &e) < 6) + { + rsrc2 = ((unsigned int) rsrc1) >> s; + operands[1] = GEN_INT (rsrc2); + operands[2] = GEN_INT (s); + output_asm_insn ("shlo\t%2,%1,%0\t# ldconst %3,%0", operands); + return ""; + } + } + + /* Unimplemented cases: + const is in range 0..31 but rotated around end of word: + ror 31,3,g0 -> ldconst 0xe0000003,g0 + + and any 2 instruction cases that might be worthwhile */ + + output_asm_insn ("ldconst %1,%0", operands); + return ""; +} + +/* Determine if there is an opportunity for a bypass optimization. + Bypass succeeds on the 960K* if the destination of the previous + instruction is the second operand of the current instruction. + Bypass always succeeds on the C*. + + Return 1 if the pattern should interchange the operands. + + CMPBR_FLAG is true if this is for a compare-and-branch insn. + OP1 and OP2 are the two source operands of a 3 operand insn. */ + +int +i960_bypass (insn, op1, op2, cmpbr_flag) + register rtx insn, op1, op2; + int cmpbr_flag; +{ + register rtx prev_insn, prev_dest; + + if (TARGET_C_SERIES) + return 0; + + /* Can't do this if op1 isn't a register. */ + if (! REG_P (op1)) + return 0; + + /* Can't do this for a compare-and-branch if both ops aren't regs. */ + if (cmpbr_flag && ! REG_P (op2)) + return 0; + + prev_insn = prev_real_insn (insn); + + if (prev_insn && GET_CODE (prev_insn) == INSN + && GET_CODE (PATTERN (prev_insn)) == SET) + { + prev_dest = SET_DEST (PATTERN (prev_insn)); + if ((GET_CODE (prev_dest) == REG && REGNO (prev_dest) == REGNO (op1)) + || (GET_CODE (prev_dest) == SUBREG + && GET_CODE (SUBREG_REG (prev_dest)) == REG + && REGNO (SUBREG_REG (prev_dest)) == REGNO (op1))) + return 1; + } + return 0; +} + +/* Output the code which declares the function name. This also handles + leaf routines, which have special requirements, and initializes some + global variables. */ + +void +i960_function_name_declare (file, name, fndecl) + FILE *file; + const char *name; + tree fndecl; +{ + register int i, j; + int leaf_proc_ok; + rtx insn; + + /* Increment global return label. */ + + ret_label++; + + /* Compute whether tail calls and leaf routine optimizations can be performed + for this function. */ + + if (TARGET_TAILCALL) + tail_call_ok = 1; + else + tail_call_ok = 0; + + if (TARGET_LEAFPROC) + leaf_proc_ok = 1; + else + leaf_proc_ok = 0; + + /* Even if nobody uses extra parms, can't have leafproc or tail calls if + argblock, because argblock uses g14 implicitly. */ + + if (current_function_args_size != 0 || VARARGS_STDARG_FUNCTION (fndecl)) + { + tail_call_ok = 0; + leaf_proc_ok = 0; + } + + /* See if caller passes in an address to return value. */ + + if (aggregate_value_p (DECL_RESULT (fndecl), fndecl)) + { + tail_call_ok = 0; + leaf_proc_ok = 0; + } + + /* Can not use tail calls or make this a leaf routine if there is a non + zero frame size. */ + + if (get_frame_size () != 0) + leaf_proc_ok = 0; + + /* I don't understand this condition, and do not think that it is correct. + Apparently this is just checking whether the frame pointer is used, and + we can't trust regs_ever_live[fp] since it is (almost?) always set. */ + + if (tail_call_ok) + for (insn = get_insns (); insn; insn = NEXT_INSN (insn)) + if (GET_CODE (insn) == INSN + && reg_mentioned_p (frame_pointer_rtx, insn)) + { + tail_call_ok = 0; + break; + } + + /* Check for CALL insns. Can not be a leaf routine if there are any. */ + + if (leaf_proc_ok) + for (insn = get_insns (); insn; insn = NEXT_INSN (insn)) + if (GET_CODE (insn) == CALL_INSN) + { + leaf_proc_ok = 0; + break; + } + + /* Can not be a leaf routine if any non-call clobbered registers are + used in this function. */ + + if (leaf_proc_ok) + for (i = 0, j = 0; i < FIRST_PSEUDO_REGISTER; i++) + if (regs_ever_live[i] + && ((! call_used_regs[i]) || (i > 7 && i < 12))) + { + /* Global registers. */ + if (i < 16 && i > 7 && i != 13) + leaf_proc_ok = 0; + /* Local registers. */ + else if (i < 32) + leaf_proc_ok = 0; + } + + /* Now choose a leaf return register, if we can find one, and if it is + OK for this to be a leaf routine. */ + + i960_leaf_ret_reg = -1; + + if (optimize && leaf_proc_ok) + { + for (i960_leaf_ret_reg = -1, i = 0; i < 8; i++) + if (regs_ever_live[i] == 0) + { + i960_leaf_ret_reg = i; + regs_ever_live[i] = 1; + break; + } + } + + /* Do this after choosing the leaf return register, so it will be listed + if one was chosen. */ + + fprintf (file, "\t# Function '%s'\n", (name[0] == '*' ? &name[1] : name)); + fprintf (file, "\t# Registers used: "); + + for (i = 0, j = 0; i < FIRST_PSEUDO_REGISTER; i++) + { + if (regs_ever_live[i]) + { + fprintf (file, "%s%s ", reg_names[i], call_used_regs[i] ? "" : "*"); + + if (i > 15 && j == 0) + { + fprintf (file,"\n\t#\t\t "); + j++; + } + } + } + + fprintf (file, "\n"); + + if (i960_leaf_ret_reg >= 0) + { + /* Make it a leaf procedure. */ + + if (TREE_PUBLIC (fndecl)) + fprintf (file,"\t.globl\t%s.lf\n", (name[0] == '*' ? &name[1] : name)); + + fprintf (file, "\t.leafproc\t"); + assemble_name (file, name); + fprintf (file, ",%s.lf\n", (name[0] == '*' ? &name[1] : name)); + ASM_OUTPUT_LABEL (file, name); + fprintf (file, "\tlda Li960R%d,g14\n", ret_label); + fprintf (file, "%s.lf:\n", (name[0] == '*' ? &name[1] : name)); + fprintf (file, "\tmov g14,g%d\n", i960_leaf_ret_reg); + + if (TARGET_C_SERIES) + { + fprintf (file, "\tlda 0,g14\n"); + i960_last_insn_type = I_TYPE_MEM; + } + else + { + fprintf (file, "\tmov 0,g14\n"); + i960_last_insn_type = I_TYPE_REG; + } + } + else + { + ASM_OUTPUT_LABEL (file, name); + i960_last_insn_type = I_TYPE_CTRL; + } +} + +/* Compute and return the frame size. */ + +int +compute_frame_size (size) + int size; +{ + int actual_fsize; + int outgoing_args_size = current_function_outgoing_args_size; + + /* The STARTING_FRAME_OFFSET is totally hidden to us as far + as size is concerned. */ + actual_fsize = (size + 15) & -16; + actual_fsize += (outgoing_args_size + 15) & -16; + + return actual_fsize; +} + +/* Here register group is range of registers which can be moved by + one i960 instruction. */ + +struct reg_group +{ + char start_reg; + char length; +}; + +static int i960_form_reg_groups (int, int, int *, int, struct reg_group *); +static int i960_reg_group_compare (const void *, const void *); +static int i960_split_reg_group (struct reg_group *, int, int); +static void i960_arg_size_and_align (enum machine_mode, tree, int *, int *); + +/* The following functions forms the biggest as possible register + groups with registers in STATE. REGS contain states of the + registers in range [start, finish_reg). The function returns the + number of groups formed. */ +static int +i960_form_reg_groups (start_reg, finish_reg, regs, state, reg_groups) + int start_reg; + int finish_reg; + int *regs; + int state; + struct reg_group *reg_groups; +{ + int i; + int nw = 0; + + for (i = start_reg; i < finish_reg; ) + { + if (regs [i] != state) + { + i++; + continue; + } + else if (i % 2 != 0 || regs [i + 1] != state) + reg_groups [nw].length = 1; + else if (i % 4 != 0 || regs [i + 2] != state) + reg_groups [nw].length = 2; + else if (regs [i + 3] != state) + reg_groups [nw].length = 3; + else + reg_groups [nw].length = 4; + reg_groups [nw].start_reg = i; + i += reg_groups [nw].length; + nw++; + } + return nw; +} + +/* We sort register winodws in descending order by length. */ +static int +i960_reg_group_compare (group1, group2) + const void *group1; + const void *group2; +{ + const struct reg_group *w1 = group1; + const struct reg_group *w2 = group2; + + if (w1->length > w2->length) + return -1; + else if (w1->length < w2->length) + return 1; + else + return 0; +} + +/* Split the first register group in REG_GROUPS on subgroups one of + which will contain SUBGROUP_LENGTH registers. The function + returns new number of winodws. */ +static int +i960_split_reg_group (reg_groups, nw, subgroup_length) + struct reg_group *reg_groups; + int nw; + int subgroup_length; +{ + if (subgroup_length < reg_groups->length - subgroup_length) + /* This guarantees correct alignments of the two subgroups for + i960 (see spliting for the group length 2, 3, 4). More + generalized algorithm would require splitting the group more + two subgroups. */ + subgroup_length = reg_groups->length - subgroup_length; + /* More generalized algorithm would require to try merging + subgroups here. But in case i960 it always results in failure + because of register group alignment. */ + reg_groups[nw].length = reg_groups->length - subgroup_length; + reg_groups[nw].start_reg = reg_groups->start_reg + subgroup_length; + nw++; + reg_groups->length = subgroup_length; + qsort (reg_groups, nw, sizeof (struct reg_group), i960_reg_group_compare); + return nw; +} + +/* Output code for the function prologue. */ + +static void +i960_output_function_prologue (file, size) + FILE *file; + HOST_WIDE_INT size; +{ + register int i, j, nr; + int n_saved_regs = 0; + int n_remaining_saved_regs; + HOST_WIDE_INT lvar_size; + HOST_WIDE_INT actual_fsize, offset; + int gnw, lnw; + struct reg_group *g, *l; + char tmpstr[1000]; + /* -1 if reg must be saved on proc entry, 0 if available, 1 if saved + somewhere. */ + int regs[FIRST_PSEUDO_REGISTER]; + /* All global registers (which must be saved) divided by groups. */ + struct reg_group global_reg_groups [16]; + /* All local registers (which are available) divided by groups. */ + struct reg_group local_reg_groups [16]; + + + for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) + if (regs_ever_live[i] + && ((! call_used_regs[i]) || (i > 7 && i < 12)) + /* No need to save the static chain pointer. */ + && ! (i == STATIC_CHAIN_REGNUM && current_function_needs_context)) + { + regs[i] = -1; + /* Count global registers that need saving. */ + if (i < 16) + n_saved_regs++; + } + else + regs[i] = 0; + + n_remaining_saved_regs = n_saved_regs; + + epilogue_string[0] = '\0'; + + if (current_function_profile) + { + /* When profiling, we may use registers 20 to 27 to save arguments, so + they can't be used here for saving globals. J is the number of + argument registers the mcount call will save. */ + for (j = 7; j >= 0 && ! regs_ever_live[j]; j--) + ; + + for (i = 20; i <= j + 20; i++) + regs[i] = -1; + } + + gnw = i960_form_reg_groups (0, 16, regs, -1, global_reg_groups); + lnw = i960_form_reg_groups (19, 32, regs, 0, local_reg_groups); + qsort (global_reg_groups, gnw, sizeof (struct reg_group), + i960_reg_group_compare); + qsort (local_reg_groups, lnw, sizeof (struct reg_group), + i960_reg_group_compare); + for (g = global_reg_groups, l = local_reg_groups; lnw != 0 && gnw != 0;) + { + if (g->length == l->length) + { + fprintf (file, "\tmov%s %s,%s\n", + ((g->length == 4) ? "q" : + (g->length == 3) ? "t" : + (g->length == 2) ? "l" : ""), + reg_names[(unsigned char) g->start_reg], + reg_names[(unsigned char) l->start_reg]); + sprintf (tmpstr, "\tmov%s %s,%s\n", + ((g->length == 4) ? "q" : + (g->length == 3) ? "t" : + (g->length == 2) ? "l" : ""), + reg_names[(unsigned char) l->start_reg], + reg_names[(unsigned char) g->start_reg]); + strcat (epilogue_string, tmpstr); + n_remaining_saved_regs -= g->length; + for (i = 0; i < g->length; i++) + { + regs [i + g->start_reg] = 1; + regs [i + l->start_reg] = -1; + regs_ever_live [i + l->start_reg] = 1; + } + g++; + l++; + gnw--; + lnw--; + } + else if (g->length > l->length) + gnw = i960_split_reg_group (g, gnw, l->length); + else + lnw = i960_split_reg_group (l, lnw, g->length); + } + + actual_fsize = compute_frame_size (size) + 4 * n_remaining_saved_regs; +#if 0 + /* ??? The 1.2.1 compiler does this also. This is meant to round the frame + size up to the nearest multiple of 16. I don't know whether this is + necessary, or even desirable. + + The frame pointer must be aligned, but the call instruction takes care of + that. If we leave the stack pointer unaligned, we may save a little on + dynamic stack allocation. And we don't lose, at least according to the + i960CA manual. */ + actual_fsize = (actual_fsize + 15) & ~0xF; +#endif + + /* Check stack limit if necessary. */ + if (current_function_limit_stack) + { + rtx min_stack = stack_limit_rtx; + if (actual_fsize != 0) + min_stack = plus_constant (stack_limit_rtx, -actual_fsize); + + /* Now, emulate a little bit of reload. We want to turn 'min_stack' + into an arith_operand. Use register 20 as the temporary. */ + if (legitimate_address_p (Pmode, min_stack, 1) + && !arith_operand (min_stack, Pmode)) + { + rtx tmp = gen_rtx_MEM (Pmode, min_stack); + fputs ("\tlda\t", file); + i960_print_operand (file, tmp, 0); + fputs (",r4\n", file); + min_stack = gen_rtx_REG (Pmode, 20); + } + if (arith_operand (min_stack, Pmode)) + { + fputs ("\tcmpo\tsp,", file); + i960_print_operand (file, min_stack, 0); + fputs ("\n\tfaultge.f\n", file); + } + else + warning ("stack limit expression is not supported"); + } + + /* Allocate space for register save and locals. */ + if (actual_fsize > 0) + { + if (actual_fsize < 32) + fprintf (file, "\taddo " HOST_WIDE_INT_PRINT_DEC ",sp,sp\n", + actual_fsize); + else + fprintf (file, "\tlda\t" HOST_WIDE_INT_PRINT_DEC "(sp),sp\n", + actual_fsize); + } + + /* Take hardware register save area created by the call instruction + into account, but store them before the argument block area. */ + lvar_size = actual_fsize - compute_frame_size (0) - n_remaining_saved_regs * 4; + offset = STARTING_FRAME_OFFSET + lvar_size; + /* Save registers on stack if needed. */ + /* ??? Is it worth to use the same algorithm as one for saving + global registers in local registers? */ + for (i = 0, j = n_remaining_saved_regs; j > 0 && i < 16; i++) + { + if (regs[i] != -1) + continue; + + nr = 1; + + if (i <= 14 && i % 2 == 0 && regs[i+1] == -1 && offset % 2 == 0) + nr = 2; + + if (nr == 2 && i <= 12 && i % 4 == 0 && regs[i+2] == -1 + && offset % 4 == 0) + nr = 3; + + if (nr == 3 && regs[i+3] == -1) + nr = 4; + + fprintf (file,"\tst%s %s," HOST_WIDE_INT_PRINT_DEC "(fp)\n", + ((nr == 4) ? "q" : + (nr == 3) ? "t" : + (nr == 2) ? "l" : ""), + reg_names[i], offset); + sprintf (tmpstr,"\tld%s " HOST_WIDE_INT_PRINT_DEC "(fp),%s\n", + ((nr == 4) ? "q" : + (nr == 3) ? "t" : + (nr == 2) ? "l" : ""), + offset, reg_names[i]); + strcat (epilogue_string, tmpstr); + i += nr-1; + j -= nr; + offset += nr * 4; + } + + if (actual_fsize == 0) + return; + + fprintf (file, "\t#Prologue stats:\n"); + fprintf (file, "\t# Total Frame Size: " HOST_WIDE_INT_PRINT_DEC " bytes\n", + actual_fsize); + + if (lvar_size) + fprintf (file, "\t# Local Variable Size: " HOST_WIDE_INT_PRINT_DEC + " bytes\n", lvar_size); + if (n_saved_regs) + fprintf (file, "\t# Register Save Size: %d regs, %d bytes\n", + n_saved_regs, n_saved_regs * 4); + fprintf (file, "\t#End Prologue#\n"); +} + +/* Output code for the function profiler. */ + +void +output_function_profiler (file, labelno) + FILE *file; + int labelno; +{ + /* The last used parameter register. */ + int last_parm_reg; + int i, j, increment; + int varargs_stdarg_function + = VARARGS_STDARG_FUNCTION (current_function_decl); + + /* Figure out the last used parameter register. The proper thing to do + is to walk incoming args of the function. A function might have live + parameter registers even if it has no incoming args. Note that we + don't have to save parameter registers g8 to g11 because they are + call preserved. */ + + /* See also output_function_prologue, which tries to use local registers + for preserved call-saved global registers. */ + + for (last_parm_reg = 7; + last_parm_reg >= 0 && ! regs_ever_live[last_parm_reg]; + last_parm_reg--) + ; + + /* Save parameter registers in regs r4 (20) to r11 (27). */ + + for (i = 0, j = 4; i <= last_parm_reg; i += increment, j += increment) + { + if (i % 4 == 0 && (last_parm_reg - i) >= 3) + increment = 4; + else if (i % 4 == 0 && (last_parm_reg - i) >= 2) + increment = 3; + else if (i % 2 == 0 && (last_parm_reg - i) >= 1) + increment = 2; + else + increment = 1; + + fprintf (file, "\tmov%s g%d,r%d\n", + (increment == 4 ? "q" : increment == 3 ? "t" + : increment == 2 ? "l": ""), i, j); + } + + /* If this function uses the arg pointer, then save it in r3 and then + set it to zero. */ + + if (current_function_args_size != 0 || varargs_stdarg_function) + fprintf (file, "\tmov g14,r3\n\tmov 0,g14\n"); + + /* Load location address into g0 and call mcount. */ + + fprintf (file, "\tlda\tLP%d,g0\n\tcallx\tmcount\n", labelno); + + /* If this function uses the arg pointer, restore it. */ + + if (current_function_args_size != 0 || varargs_stdarg_function) + fprintf (file, "\tmov r3,g14\n"); + + /* Restore parameter registers. */ + + for (i = 0, j = 4; i <= last_parm_reg; i += increment, j += increment) + { + if (i % 4 == 0 && (last_parm_reg - i) >= 3) + increment = 4; + else if (i % 4 == 0 && (last_parm_reg - i) >= 2) + increment = 3; + else if (i % 2 == 0 && (last_parm_reg - i) >= 1) + increment = 2; + else + increment = 1; + + fprintf (file, "\tmov%s r%d,g%d\n", + (increment == 4 ? "q" : increment == 3 ? "t" + : increment == 2 ? "l": ""), j, i); + } +} + +/* Output code for the function epilogue. */ + +static void +i960_output_function_epilogue (file, size) + FILE *file; + HOST_WIDE_INT size ATTRIBUTE_UNUSED; +{ + if (i960_leaf_ret_reg >= 0) + { + fprintf (file, "Li960R%d: ret\n", ret_label); + return; + } + + if (*epilogue_string == 0) + { + register rtx tmp; + + /* Emit a return insn, but only if control can fall through to here. */ + + tmp = get_last_insn (); + while (tmp) + { + if (GET_CODE (tmp) == BARRIER) + return; + if (GET_CODE (tmp) == CODE_LABEL) + break; + if (GET_CODE (tmp) == JUMP_INSN) + { + if (GET_CODE (PATTERN (tmp)) == RETURN) + return; + break; + } + if (GET_CODE (tmp) == NOTE) + { + tmp = PREV_INSN (tmp); + continue; + } + break; + } + fprintf (file, "Li960R%d: ret\n", ret_label); + return; + } + + fprintf (file, "Li960R%d:\n", ret_label); + + fprintf (file, "\t#EPILOGUE#\n"); + + /* Output the string created by the prologue which will restore all + registers saved by the prologue. */ + + if (epilogue_string[0] != '\0') + fprintf (file, "%s", epilogue_string); + + /* Must clear g14 on return if this function set it. + Only varargs/stdarg functions modify g14. */ + + if (VARARGS_STDARG_FUNCTION (current_function_decl)) + fprintf (file, "\tmov 0,g14\n"); + + fprintf (file, "\tret\n"); + fprintf (file, "\t#End Epilogue#\n"); +} + +/* Output code for a call insn. */ + +const char * +i960_output_call_insn (target, argsize_rtx, arg_pointer, insn) + register rtx target, argsize_rtx, arg_pointer, insn; +{ + int argsize = INTVAL (argsize_rtx); + rtx nexti = next_real_insn (insn); + rtx operands[2]; + int varargs_stdarg_function + = VARARGS_STDARG_FUNCTION (current_function_decl); + + operands[0] = target; + operands[1] = arg_pointer; + + if (current_function_args_size != 0 || varargs_stdarg_function) + output_asm_insn ("mov g14,r3", operands); + + if (argsize > 48) + output_asm_insn ("lda %a1,g14", operands); + else if (current_function_args_size != 0 || varargs_stdarg_function) + output_asm_insn ("mov 0,g14", operands); + + /* The code used to assume that calls to SYMBOL_REFs could not be more + than 24 bits away (b vs bx, callj vs callx). This is not true. This + feature is now implemented by relaxing in the GNU linker. It can convert + bx to b if in range, and callx to calls/call/balx/bal as appropriate. */ + + /* Nexti could be zero if the called routine is volatile. */ + if (optimize && (*epilogue_string == 0) && argsize == 0 && tail_call_ok + && (nexti == 0 || GET_CODE (PATTERN (nexti)) == RETURN)) + { + /* Delete following return insn. */ + if (nexti && no_labels_between_p (insn, nexti)) + delete_insn (nexti); + output_asm_insn ("bx %0", operands); + return "# notreached"; + } + + output_asm_insn ("callx %0", operands); + + /* If the caller sets g14 to the address of the argblock, then the caller + must clear it after the return. */ + + if (current_function_args_size != 0 || varargs_stdarg_function) + output_asm_insn ("mov r3,g14", operands); + else if (argsize > 48) + output_asm_insn ("mov 0,g14", operands); + + return ""; +} + +/* Output code for a return insn. */ + +const char * +i960_output_ret_insn (insn) + register rtx insn; +{ + static char lbuf[20]; + + if (*epilogue_string != 0) + { + if (! TARGET_CODE_ALIGN && next_real_insn (insn) == 0) + return ""; + + sprintf (lbuf, "b Li960R%d", ret_label); + return lbuf; + } + + /* Must clear g14 on return if this function set it. + Only varargs/stdarg functions modify g14. */ + + if (VARARGS_STDARG_FUNCTION (current_function_decl)) + output_asm_insn ("mov 0,g14", 0); + + if (i960_leaf_ret_reg >= 0) + { + sprintf (lbuf, "bx (%s)", reg_names[i960_leaf_ret_reg]); + return lbuf; + } + return "ret"; +} + +/* Print the operand represented by rtx X formatted by code CODE. */ + +void +i960_print_operand (file, x, code) + FILE *file; + rtx x; + int code; +{ + enum rtx_code rtxcode = x ? GET_CODE (x) : NIL; + + if (rtxcode == REG) + { + switch (code) + { + case 'D': + /* Second reg of a double or quad. */ + fprintf (file, "%s", reg_names[REGNO (x)+1]); + break; + + case 'E': + /* Third reg of a quad. */ + fprintf (file, "%s", reg_names[REGNO (x)+2]); + break; + + case 'F': + /* Fourth reg of a quad. */ + fprintf (file, "%s", reg_names[REGNO (x)+3]); + break; + + case 0: + fprintf (file, "%s", reg_names[REGNO (x)]); + break; + + default: + abort (); + } + return; + } + else if (rtxcode == MEM) + { + output_address (XEXP (x, 0)); + return; + } + else if (rtxcode == CONST_INT) + { + HOST_WIDE_INT val = INTVAL (x); + if (code == 'C') + val = ~val; + if (val > 9999 || val < -999) + fprintf (file, HOST_WIDE_INT_PRINT_HEX, val); + else + fprintf (file, HOST_WIDE_INT_PRINT_DEC, val); + return; + } + else if (rtxcode == CONST_DOUBLE) + { + char dstr[30]; + + if (x == CONST0_RTX (GET_MODE (x))) + { + fprintf (file, "0f0.0"); + return; + } + else if (x == CONST1_RTX (GET_MODE (x))) + { + fprintf (file, "0f1.0"); + return; + } + + real_to_decimal (dstr, CONST_DOUBLE_REAL_VALUE (x), sizeof (dstr), 0, 1); + fprintf (file, "0f%s", dstr); + return; + } + + switch(code) + { + case 'B': + /* Branch or jump, depending on assembler. */ + if (TARGET_ASM_COMPAT) + fputs ("j", file); + else + fputs ("b", file); + break; + + case 'S': + /* Sign of condition. */ + if ((rtxcode == EQ) || (rtxcode == NE) || (rtxcode == GTU) + || (rtxcode == LTU) || (rtxcode == GEU) || (rtxcode == LEU)) + fputs ("o", file); + else if ((rtxcode == GT) || (rtxcode == LT) + || (rtxcode == GE) || (rtxcode == LE)) + fputs ("i", file); + else + abort(); + break; + + case 'I': + /* Inverted condition. */ + rtxcode = reverse_condition (rtxcode); + goto normal; + + case 'X': + /* Inverted condition w/ reversed operands. */ + rtxcode = reverse_condition (rtxcode); + /* Fallthrough. */ + + case 'R': + /* Reversed operand condition. */ + rtxcode = swap_condition (rtxcode); + /* Fallthrough. */ + + case 'C': + /* Normal condition. */ + normal: + if (rtxcode == EQ) { fputs ("e", file); return; } + else if (rtxcode == NE) { fputs ("ne", file); return; } + else if (rtxcode == GT) { fputs ("g", file); return; } + else if (rtxcode == GTU) { fputs ("g", file); return; } + else if (rtxcode == LT) { fputs ("l", file); return; } + else if (rtxcode == LTU) { fputs ("l", file); return; } + else if (rtxcode == GE) { fputs ("ge", file); return; } + else if (rtxcode == GEU) { fputs ("ge", file); return; } + else if (rtxcode == LE) { fputs ("le", file); return; } + else if (rtxcode == LEU) { fputs ("le", file); return; } + else abort (); + break; + + case '+': + /* For conditional branches, substitute ".t" or ".f". */ + if (TARGET_BRANCH_PREDICT) + { + x = find_reg_note (current_output_insn, REG_BR_PROB, 0); + if (x) + { + int pred_val = INTVAL (XEXP (x, 0)); + fputs ((pred_val < REG_BR_PROB_BASE / 2 ? ".f" : ".t"), file); + } + } + break; + + case 0: + output_addr_const (file, x); + break; + + default: + abort (); + } + + return; +} + +/* Print a memory address as an operand to reference that memory location. + + This is exactly the same as legitimate_address_p, except that it the prints + addresses instead of recognizing them. */ + +void +i960_print_operand_addr (file, addr) + FILE *file; + register rtx addr; +{ + rtx breg, ireg; + rtx scale, offset; + + ireg = 0; + breg = 0; + offset = 0; + scale = const1_rtx; + + if (GET_CODE (addr) == REG) + breg = addr; + else if (CONSTANT_P (addr)) + offset = addr; + else if (GET_CODE (addr) == PLUS) + { + rtx op0, op1; + + op0 = XEXP (addr, 0); + op1 = XEXP (addr, 1); + + if (GET_CODE (op0) == REG) + { + breg = op0; + if (GET_CODE (op1) == REG) + ireg = op1; + else if (CONSTANT_P (op1)) + offset = op1; + else + abort (); + } + else if (GET_CODE (op0) == PLUS) + { + if (GET_CODE (XEXP (op0, 0)) == MULT) + { + ireg = XEXP (XEXP (op0, 0), 0); + scale = XEXP (XEXP (op0, 0), 1); + if (GET_CODE (XEXP (op0, 1)) == REG) + { + breg = XEXP (op0, 1); + offset = op1; + } + else + abort (); + } + else if (GET_CODE (XEXP (op0, 0)) == REG) + { + breg = XEXP (op0, 0); + if (GET_CODE (XEXP (op0, 1)) == REG) + { + ireg = XEXP (op0, 1); + offset = op1; + } + else + abort (); + } + else + abort (); + } + else if (GET_CODE (op0) == MULT) + { + ireg = XEXP (op0, 0); + scale = XEXP (op0, 1); + if (GET_CODE (op1) == REG) + breg = op1; + else if (CONSTANT_P (op1)) + offset = op1; + else + abort (); + } + else + abort (); + } + else if (GET_CODE (addr) == MULT) + { + ireg = XEXP (addr, 0); + scale = XEXP (addr, 1); + } + else + abort (); + + if (offset) + output_addr_const (file, offset); + if (breg) + fprintf (file, "(%s)", reg_names[REGNO (breg)]); + if (ireg) + fprintf (file, "[%s*" HOST_WIDE_INT_PRINT_DEC "]", + reg_names[REGNO (ireg)], INTVAL (scale)); +} + +/* GO_IF_LEGITIMATE_ADDRESS recognizes an RTL expression + that is a valid memory address for an instruction. + The MODE argument is the machine mode for the MEM expression + that wants to use this address. + + On 80960, legitimate addresses are: + base ld (g0),r0 + disp (12 or 32 bit) ld foo,r0 + base + index ld (g0)[g1*1],r0 + base + displ ld 0xf00(g0),r0 + base + index*scale + displ ld 0xf00(g0)[g1*4],r0 + index*scale + base ld (g0)[g1*4],r0 + index*scale + displ ld 0xf00[g1*4],r0 + index*scale ld [g1*4],r0 + index + base + displ ld 0xf00(g0)[g1*1],r0 + + In each case, scale can be 1, 2, 4, 8, or 16. */ + +/* This is exactly the same as i960_print_operand_addr, except that + it recognizes addresses instead of printing them. + + It only recognizes address in canonical form. LEGITIMIZE_ADDRESS should + convert common non-canonical forms to canonical form so that they will + be recognized. */ + +/* These two macros allow us to accept either a REG or a SUBREG anyplace + where a register is valid. */ + +#define RTX_OK_FOR_BASE_P(X, STRICT) \ + ((GET_CODE (X) == REG \ + && (STRICT ? REG_OK_FOR_BASE_P_STRICT (X) : REG_OK_FOR_BASE_P (X))) \ + || (GET_CODE (X) == SUBREG \ + && GET_CODE (SUBREG_REG (X)) == REG \ + && (STRICT ? REG_OK_FOR_BASE_P_STRICT (SUBREG_REG (X)) \ + : REG_OK_FOR_BASE_P (SUBREG_REG (X))))) + +#define RTX_OK_FOR_INDEX_P(X, STRICT) \ + ((GET_CODE (X) == REG \ + && (STRICT ? REG_OK_FOR_INDEX_P_STRICT (X) : REG_OK_FOR_INDEX_P (X)))\ + || (GET_CODE (X) == SUBREG \ + && GET_CODE (SUBREG_REG (X)) == REG \ + && (STRICT ? REG_OK_FOR_INDEX_P_STRICT (SUBREG_REG (X)) \ + : REG_OK_FOR_INDEX_P (SUBREG_REG (X))))) + +int +legitimate_address_p (mode, addr, strict) + enum machine_mode mode ATTRIBUTE_UNUSED; + register rtx addr; + int strict; +{ + if (RTX_OK_FOR_BASE_P (addr, strict)) + return 1; + else if (CONSTANT_P (addr)) + return 1; + else if (GET_CODE (addr) == PLUS) + { + rtx op0, op1; + + if (! TARGET_COMPLEX_ADDR && ! reload_completed) + return 0; + + op0 = XEXP (addr, 0); + op1 = XEXP (addr, 1); + + if (RTX_OK_FOR_BASE_P (op0, strict)) + { + if (RTX_OK_FOR_INDEX_P (op1, strict)) + return 1; + else if (CONSTANT_P (op1)) + return 1; + else + return 0; + } + else if (GET_CODE (op0) == PLUS) + { + if (GET_CODE (XEXP (op0, 0)) == MULT) + { + if (! (RTX_OK_FOR_INDEX_P (XEXP (XEXP (op0, 0), 0), strict) + && SCALE_TERM_P (XEXP (XEXP (op0, 0), 1)))) + return 0; + + if (RTX_OK_FOR_BASE_P (XEXP (op0, 1), strict) + && CONSTANT_P (op1)) + return 1; + else + return 0; + } + else if (RTX_OK_FOR_BASE_P (XEXP (op0, 0), strict)) + { + if (RTX_OK_FOR_INDEX_P (XEXP (op0, 1), strict) + && CONSTANT_P (op1)) + return 1; + else + return 0; + } + else + return 0; + } + else if (GET_CODE (op0) == MULT) + { + if (! (RTX_OK_FOR_INDEX_P (XEXP (op0, 0), strict) + && SCALE_TERM_P (XEXP (op0, 1)))) + return 0; + + if (RTX_OK_FOR_BASE_P (op1, strict)) + return 1; + else if (CONSTANT_P (op1)) + return 1; + else + return 0; + } + else + return 0; + } + else if (GET_CODE (addr) == MULT) + { + if (! TARGET_COMPLEX_ADDR && ! reload_completed) + return 0; + + return (RTX_OK_FOR_INDEX_P (XEXP (addr, 0), strict) + && SCALE_TERM_P (XEXP (addr, 1))); + } + else + return 0; +} + +/* Try machine-dependent ways of modifying an illegitimate address + to be legitimate. If we find one, return the new, valid address. + This macro is used in only one place: `memory_address' in explow.c. + + This converts some non-canonical addresses to canonical form so they + can be recognized. */ + +rtx +legitimize_address (x, oldx, mode) + register rtx x; + register rtx oldx ATTRIBUTE_UNUSED; + enum machine_mode mode ATTRIBUTE_UNUSED; +{ + if (GET_CODE (x) == SYMBOL_REF) + { + abort (); + x = copy_to_reg (x); + } + + if (! TARGET_COMPLEX_ADDR && ! reload_completed) + return x; + + /* Canonicalize (plus (mult (reg) (const)) (plus (reg) (const))) + into (plus (plus (mult (reg) (const)) (reg)) (const)). This can be + created by virtual register instantiation, register elimination, and + similar optimizations. */ + if (GET_CODE (x) == PLUS && GET_CODE (XEXP (x, 0)) == MULT + && GET_CODE (XEXP (x, 1)) == PLUS) + x = gen_rtx_PLUS (Pmode, + gen_rtx_PLUS (Pmode, XEXP (x, 0), XEXP (XEXP (x, 1), 0)), + XEXP (XEXP (x, 1), 1)); + + /* Canonicalize (plus (plus (mult (reg) (const)) (plus (reg) (const))) const) + into (plus (plus (mult (reg) (const)) (reg)) (const)). */ + else if (GET_CODE (x) == PLUS && GET_CODE (XEXP (x, 0)) == PLUS + && GET_CODE (XEXP (XEXP (x, 0), 0)) == MULT + && GET_CODE (XEXP (XEXP (x, 0), 1)) == PLUS + && CONSTANT_P (XEXP (x, 1))) + { + rtx constant, other; + + if (GET_CODE (XEXP (x, 1)) == CONST_INT) + { + constant = XEXP (x, 1); + other = XEXP (XEXP (XEXP (x, 0), 1), 1); + } + else if (GET_CODE (XEXP (XEXP (XEXP (x, 0), 1), 1)) == CONST_INT) + { + constant = XEXP (XEXP (XEXP (x, 0), 1), 1); + other = XEXP (x, 1); + } + else + constant = 0, other = 0; + + if (constant) + x = gen_rtx_PLUS (Pmode, + gen_rtx_PLUS (Pmode, XEXP (XEXP (x, 0), 0), + XEXP (XEXP (XEXP (x, 0), 1), 0)), + plus_constant (other, INTVAL (constant))); + } + + return x; +} + +#if 0 +/* Return the most stringent alignment that we are willing to consider + objects of size SIZE and known alignment ALIGN as having. */ + +int +i960_alignment (size, align) + int size; + int align; +{ + int i; + + if (! TARGET_STRICT_ALIGN) + if (TARGET_IC_COMPAT2_0 || align >= 4) + { + i = i960_object_bytes_bitalign (size) / BITS_PER_UNIT; + if (i > align) + align = i; + } + + return align; +} +#endif + + +int +hard_regno_mode_ok (regno, mode) + int regno; + enum machine_mode mode; +{ + if (regno < 32) + { + switch (mode) + { + case CCmode: case CC_UNSmode: case CC_CHKmode: + return 0; + + case DImode: case DFmode: + return (regno & 1) == 0; + + case TImode: case TFmode: + return (regno & 3) == 0; + + default: + return 1; + } + } + else if (regno >= 32 && regno < 36) + { + switch (mode) + { + case SFmode: case DFmode: case TFmode: + case SCmode: case DCmode: + return 1; + + default: + return 0; + } + } + else if (regno == 36) + { + switch (mode) + { + case CCmode: case CC_UNSmode: case CC_CHKmode: + return 1; + + default: + return 0; + } + } + else if (regno == 37) + return 0; + + abort (); +} + + +/* Return the minimum alignment of an expression rtx X in bytes. This takes + advantage of machine specific facts, such as knowing that the frame pointer + is always 16 byte aligned. */ + +int +i960_expr_alignment (x, size) + rtx x; + int size; +{ + int align = 1; + + if (x == 0) + return 1; + + switch (GET_CODE(x)) + { + case CONST_INT: + align = INTVAL(x); + + if ((align & 0xf) == 0) + align = 16; + else if ((align & 0x7) == 0) + align = 8; + else if ((align & 0x3) == 0) + align = 4; + else if ((align & 0x1) == 0) + align = 2; + else + align = 1; + break; + + case PLUS: + align = MIN (i960_expr_alignment (XEXP (x, 0), size), + i960_expr_alignment (XEXP (x, 1), size)); + break; + + case SYMBOL_REF: + /* If this is a valid program, objects are guaranteed to be + correctly aligned for whatever size the reference actually is. */ + align = i960_object_bytes_bitalign (size) / BITS_PER_UNIT; + break; + + case REG: + if (REGNO (x) == FRAME_POINTER_REGNUM) + align = 16; + break; + + case ASHIFT: + align = i960_expr_alignment (XEXP (x, 0), size); + + if (GET_CODE (XEXP (x, 1)) == CONST_INT) + { + align = align << INTVAL (XEXP (x, 1)); + align = MIN (align, 16); + } + break; + + case MULT: + align = (i960_expr_alignment (XEXP (x, 0), size) * + i960_expr_alignment (XEXP (x, 1), size)); + + align = MIN (align, 16); + break; + default: + break; + } + + return align; +} + +/* Return true if it is possible to reference both BASE and OFFSET, which + have alignment at least as great as 4 byte, as if they had alignment valid + for an object of size SIZE. */ + +int +i960_improve_align (base, offset, size) + rtx base; + rtx offset; + int size; +{ + int i, j; + + /* We have at least a word reference to the object, so we know it has to + be aligned at least to 4 bytes. */ + + i = MIN (i960_expr_alignment (base, 4), + i960_expr_alignment (offset, 4)); + + i = MAX (i, 4); + + /* We know the size of the request. If strict align is not enabled, we + can guess that the alignment is OK for the requested size. */ + + if (! TARGET_STRICT_ALIGN) + if ((j = (i960_object_bytes_bitalign (size) / BITS_PER_UNIT)) > i) + i = j; + + return (i >= size); +} + +/* Return true if it is possible to access BASE and OFFSET, which have 4 byte + (SImode) alignment as if they had 16 byte (TImode) alignment. */ + +int +i960_si_ti (base, offset) + rtx base; + rtx offset; +{ + return i960_improve_align (base, offset, 16); +} + +/* Return true if it is possible to access BASE and OFFSET, which have 4 byte + (SImode) alignment as if they had 8 byte (DImode) alignment. */ + +int +i960_si_di (base, offset) + rtx base; + rtx offset; +{ + return i960_improve_align (base, offset, 8); +} + +/* Return raw values of size and alignment (in words) for the data + type being accessed. These values will be rounded by the caller. */ + +static void +i960_arg_size_and_align (mode, type, size_out, align_out) + enum machine_mode mode; + tree type; + int *size_out; + int *align_out; +{ + int size, align; + + /* Use formal alignment requirements of type being passed, except make + it at least a word. If we don't have a type, this is a library call, + and the parm has to be of scalar type. In this case, consider its + formal alignment requirement to be its size in words. */ + + if (mode == BLKmode) + size = (int_size_in_bytes (type) + UNITS_PER_WORD - 1) / UNITS_PER_WORD; + else if (mode == VOIDmode) + { + /* End of parm list. */ + if (type == 0 || TYPE_MODE (type) != VOIDmode) + abort (); + size = 1; + } + else + size = (GET_MODE_SIZE (mode) + UNITS_PER_WORD - 1) / UNITS_PER_WORD; + + if (type == 0) + align = size; + else if (TYPE_ALIGN (type) >= BITS_PER_WORD) + align = TYPE_ALIGN (type) / BITS_PER_WORD; + else + align = 1; + + *size_out = size; + *align_out = align; +} + +/* On the 80960 the first 12 args are in registers and the rest are pushed. + Any arg that is bigger than 4 words is placed on the stack and all + subsequent arguments are placed on the stack. + + Additionally, parameters with an alignment requirement stronger than + a word must be aligned appropriately. Note that this means that a + 64 bit object with a 32 bit alignment is not 64 bit aligned and may be + passed in an odd/even register pair. */ + +/* Update CUM to advance past an argument described by MODE and TYPE. */ + +void +i960_function_arg_advance (cum, mode, type, named) + CUMULATIVE_ARGS *cum; + enum machine_mode mode; + tree type; + int named ATTRIBUTE_UNUSED; +{ + int size, align; + + i960_arg_size_and_align (mode, type, &size, &align); + + if (size > 4 || cum->ca_nstackparms != 0 + || (size + ROUND_PARM (cum->ca_nregparms, align)) > NPARM_REGS + || MUST_PASS_IN_STACK (mode, type)) + { + /* Indicate that all the registers are in use, even if all are not, + so va_start will compute the right value. */ + cum->ca_nregparms = NPARM_REGS; + cum->ca_nstackparms = ROUND_PARM (cum->ca_nstackparms, align) + size; + } + else + cum->ca_nregparms = ROUND_PARM (cum->ca_nregparms, align) + size; +} + +/* Return the register that the argument described by MODE and TYPE is + passed in, or else return 0 if it is passed on the stack. */ + +rtx +i960_function_arg (cum, mode, type, named) + CUMULATIVE_ARGS *cum; + enum machine_mode mode; + tree type; + int named ATTRIBUTE_UNUSED; +{ + rtx ret; + int size, align; + + if (mode == VOIDmode) + return 0; + + i960_arg_size_and_align (mode, type, &size, &align); + + if (size > 4 || cum->ca_nstackparms != 0 + || (size + ROUND_PARM (cum->ca_nregparms, align)) > NPARM_REGS + || MUST_PASS_IN_STACK (mode, type)) + { + cum->ca_nstackparms = ROUND_PARM (cum->ca_nstackparms, align); + ret = 0; + } + else + { + cum->ca_nregparms = ROUND_PARM (cum->ca_nregparms, align); + ret = gen_rtx_REG (mode, cum->ca_nregparms); + } + + return ret; +} + +/* Return the number of bits that an object of size N bytes is aligned to. */ + +int +i960_object_bytes_bitalign (n) + int n; +{ + if (n > 8) n = 128; + else if (n > 4) n = 64; + else if (n > 2) n = 32; + else if (n > 1) n = 16; + else n = 8; + + return n; +} + +/* Compute the alignment for an aggregate type TSIZE. + Alignment is MAX (greatest member alignment, + MIN (pragma align, structure size alignment)). */ + +int +i960_round_align (align, type) + int align; + tree type; +{ + int new_align; + tree tsize; + + if (TARGET_OLD_ALIGN || TYPE_PACKED (type)) + return align; + if (TREE_CODE (type) != RECORD_TYPE) + return align; + tsize = TYPE_SIZE (type); + + if (! tsize || TREE_CODE (tsize) != INTEGER_CST) + return align; + + new_align = i960_object_bytes_bitalign (TREE_INT_CST_LOW (tsize) + / BITS_PER_UNIT); + /* Handle #pragma align. */ + if (new_align > i960_maxbitalignment) + new_align = i960_maxbitalignment; + + if (align < new_align) + align = new_align; + + return align; +} + +/* Do any needed setup for a varargs function. For the i960, we must + create a register parameter block if one doesn't exist, and then copy + all register parameters to memory. */ + +void +i960_setup_incoming_varargs (cum, mode, type, pretend_size, no_rtl) + CUMULATIVE_ARGS *cum; + enum machine_mode mode ATTRIBUTE_UNUSED; + tree type ATTRIBUTE_UNUSED; + int *pretend_size ATTRIBUTE_UNUSED; + int no_rtl; +{ + /* Note: for a varargs fn with only a va_alist argument, this is 0. */ + int first_reg = cum->ca_nregparms; + + /* Copy only unnamed register arguments to memory. If there are + any stack parms, there are no unnamed arguments in registers, and + an argument block was already allocated by the caller. + Remember that any arg bigger than 4 words is passed on the stack as + are all subsequent args. + + If there are no stack arguments but there are exactly NPARM_REGS + registers, either there were no extra arguments or the caller + allocated an argument block. */ + + if (cum->ca_nstackparms == 0 && first_reg < NPARM_REGS && !no_rtl) + { + rtx label = gen_label_rtx (); + rtx regblock, fake_arg_pointer_rtx; + + /* Use a different rtx than arg_pointer_rtx so that cse and friends + can go on believing that the argument pointer can never be zero. */ + fake_arg_pointer_rtx = gen_raw_REG (Pmode, ARG_POINTER_REGNUM); + + /* If the argument pointer is 0, no arguments were passed on the stack + and we need to allocate a chunk to save the registers (if any + arguments were passed on the stack the caller would allocate the + 48 bytes as well). We must allocate all 48 bytes (12*4) because + va_start assumes it. */ + emit_insn (gen_cmpsi (fake_arg_pointer_rtx, const0_rtx)); + emit_jump_insn (gen_bne (label)); + emit_insn (gen_rtx_SET (VOIDmode, fake_arg_pointer_rtx, + stack_pointer_rtx)); + emit_insn (gen_rtx_SET (VOIDmode, stack_pointer_rtx, + memory_address (SImode, + plus_constant (stack_pointer_rtx, + 48)))); + emit_label (label); + + /* ??? Note that we unnecessarily store one extra register for stdarg + fns. We could optimize this, but it's kept as for now. */ + regblock = gen_rtx_MEM (BLKmode, + plus_constant (arg_pointer_rtx, first_reg * 4)); + set_mem_alias_set (regblock, get_varargs_alias_set ()); + set_mem_align (regblock, BITS_PER_WORD); + move_block_from_reg (first_reg, regblock, + NPARM_REGS - first_reg); + } +} + +/* Define the `__builtin_va_list' type for the ABI. */ + +static tree +i960_build_builtin_va_list () +{ + return build_array_type (unsigned_type_node, + build_index_type (size_one_node)); +} + +/* Implement `va_start' for varargs and stdarg. */ + +void +i960_va_start (valist, nextarg) + tree valist; + rtx nextarg ATTRIBUTE_UNUSED; +{ + tree s, t, base, num; + rtx fake_arg_pointer_rtx; + + /* The array type always decays to a pointer before we get here, so we + can't use ARRAY_REF. */ + base = build1 (INDIRECT_REF, unsigned_type_node, valist); + num = build1 (INDIRECT_REF, unsigned_type_node, + build (PLUS_EXPR, unsigned_type_node, valist, + TYPE_SIZE_UNIT (TREE_TYPE (valist)))); + + /* Use a different rtx than arg_pointer_rtx so that cse and friends + can go on believing that the argument pointer can never be zero. */ + fake_arg_pointer_rtx = gen_raw_REG (Pmode, ARG_POINTER_REGNUM); + s = make_tree (unsigned_type_node, fake_arg_pointer_rtx); + t = build (MODIFY_EXPR, unsigned_type_node, base, s); + TREE_SIDE_EFFECTS (t) = 1; + expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL); + + s = build_int_2 ((current_function_args_info.ca_nregparms + + current_function_args_info.ca_nstackparms) * 4, 0); + t = build (MODIFY_EXPR, unsigned_type_node, num, s); + TREE_SIDE_EFFECTS (t) = 1; + expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL); +} + +/* Implement `va_arg'. */ + +rtx +i960_va_arg (valist, type) + tree valist, type; +{ + HOST_WIDE_INT siz, ali; + tree base, num, pad, next, this, t1, t2, int48; + rtx addr_rtx; + + /* The array type always decays to a pointer before we get here, so we + can't use ARRAY_REF. */ + base = build1 (INDIRECT_REF, unsigned_type_node, valist); + num = build1 (INDIRECT_REF, unsigned_type_node, + build (PLUS_EXPR, unsigned_type_node, valist, + TYPE_SIZE_UNIT (TREE_TYPE (valist)))); + + /* Round up sizeof(type) to a word. */ + siz = (int_size_in_bytes (type) + UNITS_PER_WORD - 1) & -UNITS_PER_WORD; + + /* Round up alignment to a word. */ + ali = TYPE_ALIGN (type); + if (ali < BITS_PER_WORD) + ali = BITS_PER_WORD; + ali /= BITS_PER_UNIT; + + /* Align NUM appropriate for the argument. */ + pad = fold (build (PLUS_EXPR, unsigned_type_node, num, + build_int_2 (ali - 1, 0))); + pad = fold (build (BIT_AND_EXPR, unsigned_type_node, pad, + build_int_2 (-ali, -1))); + pad = save_expr (pad); + + /* Increment VPAD past this argument. */ + next = fold (build (PLUS_EXPR, unsigned_type_node, pad, + build_int_2 (siz, 0))); + next = save_expr (next); + + /* Find the offset for the current argument. Mind peculiar overflow + from registers to stack. */ + int48 = build_int_2 (48, 0); + if (siz > 16) + t2 = integer_one_node; + else + t2 = fold (build (GT_EXPR, integer_type_node, next, int48)); + t1 = fold (build (LE_EXPR, integer_type_node, num, int48)); + t1 = fold (build (TRUTH_AND_EXPR, integer_type_node, t1, t2)); + this = fold (build (COND_EXPR, unsigned_type_node, t1, int48, pad)); + + /* Find the address for the current argument. */ + t1 = fold (build (PLUS_EXPR, unsigned_type_node, base, this)); + t1 = build1 (NOP_EXPR, ptr_type_node, t1); + addr_rtx = expand_expr (t1, NULL_RTX, Pmode, EXPAND_NORMAL); + + /* Increment NUM. */ + t1 = build (MODIFY_EXPR, unsigned_type_node, num, next); + TREE_SIDE_EFFECTS (t1) = 1; + expand_expr (t1, const0_rtx, VOIDmode, EXPAND_NORMAL); + + return addr_rtx; +} + +/* Calculate the final size of the reg parm stack space for the current + function, based on how many bytes would be allocated on the stack. */ + +int +i960_final_reg_parm_stack_space (const_size, var_size) + int const_size; + tree var_size; +{ + if (var_size || const_size > 48) + return 48; + else + return 0; +} + +/* Calculate the size of the reg parm stack space. This is a bit complicated + on the i960. */ + +int +i960_reg_parm_stack_space (fndecl) + tree fndecl; +{ + /* In this case, we are called from emit_library_call, and we don't need + to pretend we have more space for parameters than what's apparent. */ + if (fndecl == 0) + return 0; + + /* In this case, we are called from locate_and_pad_parms when we're + not IN_REGS, so we have an arg block. */ + if (fndecl != current_function_decl) + return 48; + + /* Otherwise, we have an arg block if the current function has more than + 48 bytes of parameters. */ + if (current_function_args_size != 0 || VARARGS_STDARG_FUNCTION (fndecl)) + return 48; + else + return 0; +} + +/* Return the register class of a scratch register needed to copy IN into + or out of a register in CLASS in MODE. If it can be done directly, + NO_REGS is returned. */ + +enum reg_class +secondary_reload_class (class, mode, in) + enum reg_class class; + enum machine_mode mode; + rtx in; +{ + int regno = -1; + + if (GET_CODE (in) == REG || GET_CODE (in) == SUBREG) + regno = true_regnum (in); + + /* We can place anything into LOCAL_OR_GLOBAL_REGS and can put + LOCAL_OR_GLOBAL_REGS into anything. */ + if (class == LOCAL_OR_GLOBAL_REGS || class == LOCAL_REGS + || class == GLOBAL_REGS || (regno >= 0 && regno < 32)) + return NO_REGS; + + /* We can place any hard register, 0.0, and 1.0 into FP_REGS. */ + if (class == FP_REGS + && ((regno >= 0 && regno < FIRST_PSEUDO_REGISTER) + || in == CONST0_RTX (mode) || in == CONST1_RTX (mode))) + return NO_REGS; + + return LOCAL_OR_GLOBAL_REGS; +} + +/* Look at the opcode P, and set i96_last_insn_type to indicate which + function unit it executed on. */ + +/* ??? This would make more sense as an attribute. */ + +void +i960_scan_opcode (p) + const char *p; +{ + switch (*p) + { + case 'a': + case 'd': + case 'e': + case 'm': + case 'n': + case 'o': + case 'r': + /* Ret is not actually of type REG, but it won't matter, because no + insn will ever follow it. */ + case 'u': + case 'x': + i960_last_insn_type = I_TYPE_REG; + break; + + case 'b': + if (p[1] == 'x' || p[3] == 'x') + i960_last_insn_type = I_TYPE_MEM; + i960_last_insn_type = I_TYPE_CTRL; + break; + + case 'f': + case 't': + i960_last_insn_type = I_TYPE_CTRL; + break; + + case 'c': + if (p[1] == 'a') + { + if (p[4] == 'x') + i960_last_insn_type = I_TYPE_MEM; + else + i960_last_insn_type = I_TYPE_CTRL; + } + else if (p[1] == 'm') + { + if (p[3] == 'd') + i960_last_insn_type = I_TYPE_REG; + else if (p[4] == 'b' || p[4] == 'j') + i960_last_insn_type = I_TYPE_CTRL; + else + i960_last_insn_type = I_TYPE_REG; + } + else + i960_last_insn_type = I_TYPE_REG; + break; + + case 'l': + i960_last_insn_type = I_TYPE_MEM; + break; + + case 's': + if (p[1] == 't') + i960_last_insn_type = I_TYPE_MEM; + else + i960_last_insn_type = I_TYPE_REG; + break; + } +} + +static void +i960_output_mi_thunk (file, thunk, delta, vcall_offset, function) + FILE *file; + tree thunk ATTRIBUTE_UNUSED; + HOST_WIDE_INT delta; + HOST_WIDE_INT vcall_offset ATTRIBUTE_UNUSED; + tree function; +{ + int d = delta; + if (d < 0 && d > -32) + fprintf (file, "\tsubo %d,g0,g0\n", -d); + else if (d > 0 && d < 32) + fprintf (file, "\taddo %d,g0,g0\n", d); + else + { + fprintf (file, "\tldconst %d,r5\n", d); + fprintf (file, "\taddo r5,g0,g0\n"); + } + fprintf (file, "\tbx "); + assemble_name (file, XSTR (XEXP (DECL_RTL (function), 0), 0)); + fprintf (file, "\n"); +} + +static bool +i960_rtx_costs (x, code, outer_code, total) + rtx x; + int code, outer_code; + int *total; +{ + switch (code) + { + /* Constants that can be (non-ldconst) insn operands are cost 0. + Constants that can be non-ldconst operands in rare cases are cost 1. + Other constants have higher costs. + + Must check for OUTER_CODE of SET for power2_operand, because + reload_cse_move2add calls us with OUTER_CODE of PLUS to decide + when to replace set with add. */ + + case CONST_INT: + if ((INTVAL (x) >= 0 && INTVAL (x) < 32) + || (outer_code == SET && power2_operand (x, VOIDmode))) + { + *total = 0; + return true; + } + else if (INTVAL (x) >= -31 && INTVAL (x) < 0) + { + *total = 1; + return true; + } + /* FALLTHRU */ + + case CONST: + case LABEL_REF: + case SYMBOL_REF: + *total = (TARGET_C_SERIES ? 6 : 8); + return true; + + case CONST_DOUBLE: + if (x == CONST0_RTX (DFmode) || x == CONST0_RTX (SFmode) + || x == CONST1_RTX (DFmode) || x == CONST1_RTX (SFmode)) + *total = 1; + else + *total = 12; + return true; + + default: + return false; + } +} |