diff options
author | YamaArashi <shadow962@live.com> | 2016-01-06 01:47:28 -0800 |
---|---|---|
committer | YamaArashi <shadow962@live.com> | 2016-01-06 01:47:28 -0800 |
commit | be8b04496302184c6e8f04d6179f9c3afc50aeb6 (patch) | |
tree | 726e2468c0c07add773c0dbd86ab6386844259ae /gcc/config/v850/v850.c |
initial commit
Diffstat (limited to 'gcc/config/v850/v850.c')
-rwxr-xr-x | gcc/config/v850/v850.c | 3673 |
1 files changed, 3673 insertions, 0 deletions
diff --git a/gcc/config/v850/v850.c b/gcc/config/v850/v850.c new file mode 100755 index 0000000..60cb9e1 --- /dev/null +++ b/gcc/config/v850/v850.c @@ -0,0 +1,3673 @@ +/* Subroutines for insn-output.c for NEC V850 series + Copyright (C) 1996, 1997, 1998, 1999 Free Software Foundation, Inc. + Contributed by Jeff Law (law@cygnus.com). + +This file is part of GNU CC. + +GNU CC is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU CC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU CC; see the file COPYING. If not, write to +the Free Software Foundation, 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. */ + +#include <stdio.h> +#include <ctype.h> +#include "config.h" +#include "tree.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 "output.h" +#include "insn-attr.h" +#include "flags.h" +#include "recog.h" +#include "expr.h" +#include "obstack.h" +#include "toplev.h" + +#ifndef streq +#define streq(a,b) (strcmp (a, b) == 0) +#endif + +/* Function prototypes that cannot exist in v850.h due to dependency + compilcations. */ +extern rtx function_arg + PROTO ((CUMULATIVE_ARGS *, enum machine_mode, tree, int)); +extern int function_arg_partial_nregs + PROTO ((CUMULATIVE_ARGS *, enum machine_mode, tree, int)); +extern void asm_file_start PROTO ((FILE *)); +extern void print_operand PROTO ((FILE *, rtx, int )); +extern void print_operand_address PROTO ((FILE *, rtx)); +extern void v850_output_aligned_bss + PROTO ((FILE *, tree, char *, int, int)); +extern void v850_output_common + PROTO ((FILE *, tree, char *, int, int)); +extern void v850_output_local + PROTO ((FILE *, tree, char *, int, int)); +extern int const_costs PROTO ((rtx, enum rtx_code)); +extern char * output_move_double PROTO ((rtx *)); +extern char * output_move_single PROTO ((rtx *)); +extern int ep_memory_operand + PROTO ((rtx, enum machine_mode, int)); +extern int reg_or_0_operand PROTO ((rtx, enum machine_mode)); +extern int reg_or_int5_operand PROTO ((rtx, enum machine_mode)); +extern int call_address_operand PROTO ((rtx, enum machine_mode)); +extern int movsi_source_operand PROTO ((rtx, enum machine_mode)); +extern int power_of_two_operand PROTO ((rtx, enum machine_mode)); +extern int not_power_of_two_operand PROTO ((rtx, enum machine_mode)); +extern int special_symbolref_operand PROTO ((rtx, enum machine_mode)); +extern void v850_reorg PROTO ((rtx)); +extern void notice_update_cc PROTO ((rtx, rtx)); +extern int v850_valid_machine_decl_attribute + PROTO ((tree, tree, tree)); +extern int v850_interrupt_function_p PROTO ((tree)); +extern int pattern_is_ok_for_prologue PROTO ((rtx, enum machine_mode)); +extern int pattern_is_ok_for_epilogue PROTO ((rtx, enum machine_mode)); +extern int register_is_ok_for_epilogue PROTO ((rtx, enum machine_mode)); +extern char * construct_save_jarl PROTO ((rtx)); +extern char * construct_restore_jr PROTO ((rtx)); +extern void v850_encode_data_area PROTO ((tree)); +extern void v850_set_default_decl_attr PROTO ((tree)); + +/* Function prototypes for stupid compilers: */ +static void const_double_split + PROTO ((rtx, HOST_WIDE_INT *, HOST_WIDE_INT *)); +static int const_costs_int PROTO ((HOST_WIDE_INT, int)); +static void substitute_ep_register PROTO ((rtx, rtx, int, int, rtx *, rtx *)); +static int push_data_area PROTO ((v850_data_area)); +static int pop_data_area PROTO ((v850_data_area)); +static int parse_ghs_pragma_token PROTO ((char *)); +static int ep_memory_offset PROTO ((enum machine_mode, int)); +static int mark_current_function_as_interrupt PROTO ((void)); +static void v850_set_data_area PROTO ((tree, v850_data_area)); +/* CYGNUS LOCAL v850e */ +extern int pattern_is_ok_for_prepare PROTO ((rtx, enum machine_mode)); +extern int pattern_is_ok_for_dispose PROTO ((rtx, enum machine_mode)); +extern char * construct_dispose_instruction PROTO ((rtx)); +extern char * construct_prepare_instruction PROTO ((rtx)); +/* END CYGNUS LOCAL */ + +/* True if the current function has anonymous arguments. */ +int current_function_anonymous_args; + +/* Information about the various small memory areas. */ +struct small_memory_info small_memory[ (int)SMALL_MEMORY_max ] = +{ + /* name value max physical max */ + { "tda", (char *)0, 0, 256 }, + { "sda", (char *)0, 0, 65536 }, + { "zda", (char *)0, 0, 32768 }, +}; + +/* True if we don't need to check any more if the current + function is an interrupt handler */ +static int v850_interrupt_cache_p = FALSE; + +/* Whether current function is an interrupt handler. */ +static int v850_interrupt_p = FALSE; + + +/* Sometimes certain combinations of command options do not make + sense on a particular target machine. You can define a macro + `OVERRIDE_OPTIONS' to take account of this. This macro, if + defined, is executed once just after all the command options have + been parsed. + + Don't use this macro to turn on various extra optimizations for + `-O'. That is what `OPTIMIZATION_OPTIONS' is for. */ + +void +override_options () +{ + int i; + extern int atoi PROTO ((const char *)); + + /* Parse -m{s,t,z}da=nnn switches */ + for (i = 0; i < (int)SMALL_MEMORY_max; i++) + { + if (small_memory[i].value) + { + if (!isdigit (*small_memory[i].value)) + error ("%s=%s is not numeric.", + small_memory[i].name, + small_memory[i].value); + else + { + small_memory[i].max = atoi (small_memory[i].value); + if (small_memory[i].max > small_memory[i].physical_max) + error ("%s=%s is too large.", + small_memory[i].name, + small_memory[i].value); + } + } + } +/* CYGNUS LOCAL v850e */ +#ifdef MASK_US_BIT_SET + /* Make sure that the US_BIT_SET mask has been correctly initialised. */ + if ((target_flags & MASK_US_MASK_SET) == 0) + { + if (TARGET_V850EA) + target_flags |= (MASK_US_MASK_SET | MASK_US_BIT_SET); + else + { + target_flags |= MASK_US_MASK_SET; + target_flags &= ~MASK_US_BIT_SET; + } + } +#endif +/* END CYGNUS LOCAL */ +} + + +/* Output assembly code for the start of the file. */ + +void +asm_file_start (file) + FILE *file; +{ + output_file_directive (file, main_input_filename); +} + + +/* Return an RTX to represent where a value with mode MODE will be returned + from a function. If the result is 0, the argument is pushed. */ + +rtx +function_arg (cum, mode, type, named) + CUMULATIVE_ARGS *cum; + enum machine_mode mode; + tree type; + int named; +{ + rtx result = 0; + int size, align; + + if (TARGET_GHS && !named) + return NULL_RTX; + + if (mode == BLKmode) + size = int_size_in_bytes (type); + else + size = GET_MODE_SIZE (mode); + + if (type) + align = TYPE_ALIGN (type) / BITS_PER_UNIT; + else + align = size; + + cum->nbytes = (cum->nbytes + align - 1) &~(align - 1); + + if (cum->nbytes > 4 * UNITS_PER_WORD) + return 0; + + if (type == NULL_TREE + && cum->nbytes + size > 4 * UNITS_PER_WORD) + return 0; + + switch (cum->nbytes / UNITS_PER_WORD) + { + case 0: + result = gen_rtx (REG, mode, 6); + break; + case 1: + result = gen_rtx (REG, mode, 7); + break; + case 2: + result = gen_rtx (REG, mode, 8); + break; + case 3: + result = gen_rtx (REG, mode, 9); + break; + default: + result = 0; + } + + return result; +} + + +/* Return the number of words which must be put into registers + for values which are part in registers and part in memory. */ + +int +function_arg_partial_nregs (cum, mode, type, named) + CUMULATIVE_ARGS *cum; + enum machine_mode mode; + tree type; + int named; +{ + int size, align; + + if (TARGET_GHS && !named) + return 0; + + if (mode == BLKmode) + size = int_size_in_bytes (type); + else + size = GET_MODE_SIZE (mode); + + if (type) + align = TYPE_ALIGN (type) / BITS_PER_UNIT; + else + align = size; + + cum->nbytes = (cum->nbytes + align - 1) &~(align - 1); + + if (cum->nbytes > 4 * UNITS_PER_WORD) + return 0; + + if (cum->nbytes + size <= 4 * UNITS_PER_WORD) + return 0; + + if (type == NULL_TREE + && cum->nbytes + size > 4 * UNITS_PER_WORD) + return 0; + + return (4 * UNITS_PER_WORD - cum->nbytes) / UNITS_PER_WORD; +} + + +/* Return the high and low words of a CONST_DOUBLE */ + +static void +const_double_split (x, p_high, p_low) + rtx x; + HOST_WIDE_INT *p_high; + HOST_WIDE_INT *p_low; +{ + if (GET_CODE (x) == CONST_DOUBLE) + { + long t[2]; + REAL_VALUE_TYPE rv; + + switch (GET_MODE (x)) + { + case DFmode: + REAL_VALUE_FROM_CONST_DOUBLE (rv, x); + REAL_VALUE_TO_TARGET_DOUBLE (rv, t); + *p_high = t[1]; /* since v850 is little endian */ + *p_low = t[0]; /* high is second word */ + return; + + case SFmode: + REAL_VALUE_FROM_CONST_DOUBLE (rv, x); + REAL_VALUE_TO_TARGET_SINGLE (rv, *p_high); + *p_low = 0; + return; + + case VOIDmode: + case DImode: + *p_high = CONST_DOUBLE_HIGH (x); + *p_low = CONST_DOUBLE_LOW (x); + return; + + default: + break; + } + } + + fatal_insn ("const_double_split got a bad insn:", x); +} + + +/* Return the cost of the rtx R with code CODE. */ + +static int +const_costs_int (value, zero_cost) + HOST_WIDE_INT value; + int zero_cost; +{ + if (CONST_OK_FOR_I (value)) + return zero_cost; + else if (CONST_OK_FOR_J (value)) + return 1; + else if (CONST_OK_FOR_K (value)) + return 2; + else + return 4; +} + +int +const_costs (r, c) + rtx r; + enum rtx_code c; +{ + HOST_WIDE_INT high, low; + + switch (c) + { + case CONST_INT: + return const_costs_int (INTVAL (r), 0); + + case CONST_DOUBLE: + const_double_split (r, &high, &low); + if (GET_MODE (r) == SFmode) + return const_costs_int (high, 1); + else + return const_costs_int (high, 1) + const_costs_int (low, 1); + + case SYMBOL_REF: + case LABEL_REF: + case CONST: + return 2; + + case HIGH: + return 1; + + default: + return 4; + } +} + + +/* Print operand X using operand code CODE to assembly language output file + FILE. */ + +void +print_operand (file, x, code) + FILE *file; + rtx x; + int code; +{ + HOST_WIDE_INT high, low; + + switch (code) + { + case 'c': + /* We use 'c' operands with symbols for .vtinherit */ + if (GET_CODE (x) == SYMBOL_REF) + { + output_addr_const(file, x); + break; + } + /* fall through */ + case 'b': + case 'B': + case 'C': + switch ((code == 'B' || code == 'C') + ? reverse_condition (GET_CODE (x)) : GET_CODE (x)) + { + case NE: + if (code == 'c' || code == 'C') + fprintf (file, "nz"); + else + fprintf (file, "ne"); + break; + case EQ: + if (code == 'c' || code == 'C') + fprintf (file, "z"); + else + fprintf (file, "e"); + break; + case GE: + fprintf (file, "ge"); + break; + case GT: + fprintf (file, "gt"); + break; + case LE: + fprintf (file, "le"); + break; + case LT: + fprintf (file, "lt"); + break; + case GEU: + fprintf (file, "nl"); + break; + case GTU: + fprintf (file, "h"); + break; + case LEU: + fprintf (file, "nh"); + break; + case LTU: + fprintf (file, "l"); + break; + default: + abort (); + } + break; + case 'F': /* high word of CONST_DOUBLE */ + if (GET_CODE (x) == CONST_INT) + fprintf (file, "%d", (INTVAL (x) >= 0) ? 0 : -1); + else if (GET_CODE (x) == CONST_DOUBLE) + { + const_double_split (x, &high, &low); + fprintf (file, "%ld", (long) high); + } + else + abort (); + break; + case 'G': /* low word of CONST_DOUBLE */ + if (GET_CODE (x) == CONST_INT) + fprintf (file, "%ld", (long) INTVAL (x)); + else if (GET_CODE (x) == CONST_DOUBLE) + { + const_double_split (x, &high, &low); + fprintf (file, "%ld", (long) low); + } + else + abort (); + break; + case 'L': + fprintf (file, "%d\n", INTVAL (x) & 0xffff); + break; + case 'M': + fprintf (file, "%d", exact_log2 (INTVAL (x))); + break; + case 'O': + if (special_symbolref_operand (x, VOIDmode)) + { + char* name; + + if (GET_CODE (x) == SYMBOL_REF) + name = XSTR (x, 0); + else if (GET_CODE (x) == CONST) + name = XSTR (XEXP (XEXP (x, 0), 0), 0); + else + abort (); + + if (ZDA_NAME_P (name)) + fprintf (file, "zdaoff"); + else if (SDA_NAME_P (name)) + fprintf (file, "sdaoff"); + else if (TDA_NAME_P (name)) + fprintf (file, "tdaoff"); + else + abort (); + } + else + abort (); + break; + case 'P': + if (special_symbolref_operand (x, VOIDmode)) + output_addr_const (file, x); + else + abort (); + break; + case 'Q': + if (special_symbolref_operand (x, VOIDmode)) + { + char* name; + + if (GET_CODE (x) == SYMBOL_REF) + name = XSTR (x, 0); + else if (GET_CODE (x) == CONST) + name = XSTR (XEXP (XEXP (x, 0), 0), 0); + else + abort (); + + if (ZDA_NAME_P (name)) + fprintf (file, "r0"); + else if (SDA_NAME_P (name)) + fprintf (file, "gp"); + else if (TDA_NAME_P (name)) + fprintf (file, "ep"); + else + abort (); + } + else + abort (); + break; + case 'R': /* 2nd word of a double. */ + switch (GET_CODE (x)) + { + case REG: + fprintf (file, reg_names[REGNO (x) + 1]); + break; + case MEM: + print_operand_address (file, + XEXP (adj_offsettable_operand (x, 4), 0)); + break; + + default: + break; + } + break; + case 'S': + { + /* if it's a reference to a TDA variable, use sst/sld vs. st/ld */ + if (GET_CODE (x) == MEM && ep_memory_operand (x, GET_MODE (x), FALSE)) + fputs ("s", file); + + break; + } + case 'T': + { + /* Like an 'S' operand above, but for unsigned loads only. */ + if (GET_CODE (x) == MEM && ep_memory_operand (x, GET_MODE (x), TRUE)) + fputs ("s", file); + + break; + } + case 'W': /* print the instruction suffix */ + switch (GET_MODE (x)) + { + default: + abort (); + + case QImode: fputs (".b", file); break; + case HImode: fputs (".h", file); break; + case SImode: fputs (".w", file); break; + case SFmode: fputs (".w", file); break; + } + break; + case '.': /* register r0 */ + fputs (reg_names[0], file); + break; + case 'z': /* reg or zero */ + if (x == const0_rtx) + fputs (reg_names[0], file); + else if (GET_CODE (x) == REG) + fputs (reg_names[REGNO (x)], file); + else + abort (); + break; + default: + switch (GET_CODE (x)) + { + case MEM: + if (GET_CODE (XEXP (x, 0)) == CONST_INT) + output_address (gen_rtx (PLUS, SImode, + gen_rtx (REG, SImode, 0), + XEXP (x, 0))); + else + output_address (XEXP (x, 0)); + break; + + case REG: + fputs (reg_names[REGNO (x)], file); + break; + case SUBREG: + fputs (reg_names[REGNO (SUBREG_REG (x)) + SUBREG_WORD (x)], file); + break; + case CONST_INT: + case SYMBOL_REF: + case CONST: + case LABEL_REF: + case CODE_LABEL: + print_operand_address (file, x); + break; + default: + abort (); + } + break; + + } +} + + +/* Output assembly language output for the address ADDR to FILE. */ + +void +print_operand_address (file, addr) + FILE *file; + rtx addr; +{ + switch (GET_CODE (addr)) + { + case REG: + fprintf (file, "0["); + print_operand (file, addr, 0); + fprintf (file, "]"); + break; + case LO_SUM: + if (GET_CODE (XEXP (addr, 0)) == REG) + { + /* reg,foo */ + fprintf (file, "lo("); + print_operand (file, XEXP (addr, 1), 0); + fprintf (file, ")["); + print_operand (file, XEXP (addr, 0), 0); + fprintf (file, "]"); + } + break; + case PLUS: + if (GET_CODE (XEXP (addr, 0)) == REG + || GET_CODE (XEXP (addr, 0)) == SUBREG) + { + /* reg,foo */ + print_operand (file, XEXP (addr, 1), 0); + fprintf (file, "["); + print_operand (file, XEXP (addr, 0), 0); + fprintf (file, "]"); + } + else + { + print_operand (file, XEXP (addr, 0), 0); + fprintf (file, "+"); + print_operand (file, XEXP (addr, 1), 0); + } + break; + case SYMBOL_REF: + if (ENCODED_NAME_P (XSTR (addr, 0))) + { + char* name = XSTR (addr, 0); + char* off_name; + char* reg_name; + + if (ZDA_NAME_P (name)) + { + off_name = "zdaoff"; + reg_name = "r0"; + } + else if (SDA_NAME_P (name)) + { + off_name = "sdaoff"; + reg_name = "gp"; + } + else if (TDA_NAME_P (name)) + { + off_name = "tdaoff"; + reg_name = "ep"; + } + else + abort (); + + fprintf (file, "%s(", off_name); + output_addr_const (file, addr); + fprintf (file, ")[%s]", reg_name); + } + else + output_addr_const (file, addr); + break; + case CONST: + if (special_symbolref_operand (addr, VOIDmode)) + { + char* name = XSTR (XEXP (XEXP (addr, 0), 0), 0); + char* off_name; + char* reg_name; + + if (ZDA_NAME_P (name)) + { + off_name = "zdaoff"; + reg_name = "r0"; + } + else if (SDA_NAME_P (name)) + { + off_name = "sdaoff"; + reg_name = "gp"; + } + else if (TDA_NAME_P (name)) + { + off_name = "tdaoff"; + reg_name = "ep"; + } + else + abort (); + + fprintf (file, "%s(", off_name); + output_addr_const (file, addr); + fprintf (file, ")[%s]", reg_name); + } + else + output_addr_const (file, addr); + break; + default: + output_addr_const (file, addr); + break; + } +} + + +/* Return appropriate code to load up a 1, 2, or 4 integer/floating + point value. */ + +char * +output_move_single (operands) + rtx *operands; +{ + rtx dst = operands[0]; + rtx src = operands[1]; + + if (REG_P (dst)) + { + if (REG_P (src)) + return "mov %1,%0"; + + else if (GET_CODE (src) == CONST_INT) + { + HOST_WIDE_INT value = INTVAL (src); + + if (CONST_OK_FOR_J (value)) /* signed 5 bit immediate */ + return "mov %1,%0"; + + else if (CONST_OK_FOR_K (value)) /* signed 16 bit immediate */ + return "movea lo(%1),%.,%0"; + + else if (CONST_OK_FOR_L (value)) /* upper 16 bits were set */ + return "movhi hi(%1),%.,%0"; + + else /* random constant */ +/* CYGNUS LOCAL v850e */ + if (TARGET_V850E) + return "mov %1,%0"; + else +/* END CYGNUS LOCAL */ + return "movhi hi(%1),%.,%0\n\tmovea lo(%1),%0,%0"; + } + + else if (GET_CODE (src) == CONST_DOUBLE && GET_MODE (src) == SFmode) + { + HOST_WIDE_INT high, low; + + const_double_split (src, &high, &low); + if (CONST_OK_FOR_J (high)) /* signed 5 bit immediate */ + return "mov %F1,%0"; + + else if (CONST_OK_FOR_K (high)) /* signed 16 bit immediate */ + return "movea lo(%F1),%.,%0"; + + else if (CONST_OK_FOR_L (high)) /* upper 16 bits were set */ + return "movhi hi(%F1),%.,%0"; + + else /* random constant */ +/* CYGNUS LOCAL v850e */ + if (TARGET_V850E) + return "mov %F1,%0"; + else +/* END CYGNUS LOCAL */ + return "movhi hi(%F1),%.,%0\n\tmovea lo(%F1),%0,%0"; + } + + else if (GET_CODE (src) == MEM) + return "%S1ld%W1 %1,%0"; + + else if (special_symbolref_operand (src, VOIDmode)) + return "movea %O1(%P1),%Q1,%0"; + + else if (GET_CODE (src) == LABEL_REF + || GET_CODE (src) == SYMBOL_REF + || GET_CODE (src) == CONST) + { +/* CYGNUS LOCAL v850e */ + if (TARGET_V850E) + return "mov hilo(%1),%0"; + else +/* END CYGNUS LOCAL */ + return "movhi hi(%1),%.,%0\n\tmovea lo(%1),%0,%0"; + } + + else if (GET_CODE (src) == HIGH) + return "movhi hi(%1),%.,%0"; + + else if (GET_CODE (src) == LO_SUM) + { + operands[2] = XEXP (src, 0); + operands[3] = XEXP (src, 1); + return "movea lo(%3),%2,%0"; + } + } + + else if (GET_CODE (dst) == MEM) + { + if (REG_P (src)) + return "%S0st%W0 %1,%0"; + + else if (GET_CODE (src) == CONST_INT && INTVAL (src) == 0) + return "%S0st%W0 %.,%0"; + + else if (GET_CODE (src) == CONST_DOUBLE + && CONST0_RTX (GET_MODE (dst)) == src) + return "%S0st%W0 %.,%0"; + } + + fatal_insn ("output_move_single:", gen_rtx (SET, VOIDmode, dst, src)); + return ""; +} + + +/* Return appropriate code to load up an 8 byte integer or + floating point value */ + +char * +output_move_double (operands) + rtx *operands; +{ + enum machine_mode mode = GET_MODE (operands[0]); + rtx dst = operands[0]; + rtx src = operands[1]; + + if (register_operand (dst, mode) + && register_operand (src, mode)) + { + if (REGNO (src) + 1 == REGNO (dst)) + return "mov %R1,%R0\n\tmov %1,%0"; + else + return "mov %1,%0\n\tmov %R1,%R0"; + } + + /* Storing 0 */ + if (GET_CODE (dst) == MEM + && ((GET_CODE (src) == CONST_INT && INTVAL (src) == 0) + || (GET_CODE (src) == CONST_DOUBLE && CONST_DOUBLE_OK_FOR_G (src)))) + return "st.w %.,%0\n\tst.w %.,%R0"; + + if (GET_CODE (src) == CONST_INT || GET_CODE (src) == CONST_DOUBLE) + { + HOST_WIDE_INT high_low[2]; + int i; + rtx xop[10]; + + if (GET_CODE (src) == CONST_DOUBLE) + const_double_split (src, &high_low[1], &high_low[0]); + else + { + high_low[0] = INTVAL (src); + high_low[1] = (INTVAL (src) >= 0) ? 0 : -1; + } + + for (i = 0; i < 2; i++) + { + xop[0] = gen_rtx (REG, SImode, REGNO (dst)+i); + xop[1] = GEN_INT (high_low[i]); + output_asm_insn (output_move_single (xop), xop); + } + + return ""; + } + + if (GET_CODE (src) == MEM) + { + int ptrreg = -1; + int dreg = REGNO (dst); + rtx inside = XEXP (src, 0); + + if (GET_CODE (inside) == REG) + ptrreg = REGNO (inside); + else if (GET_CODE (inside) == SUBREG) + ptrreg = REGNO (SUBREG_REG (inside)) + SUBREG_WORD (inside); + else if (GET_CODE (inside) == PLUS) + ptrreg = REGNO (XEXP (inside, 0)); + else if (GET_CODE (inside) == LO_SUM) + ptrreg = REGNO (XEXP (inside, 0)); + + if (dreg == ptrreg) + return "ld.w %R1,%R0\n\tld.w %1,%0"; + } + + if (GET_CODE (src) == MEM) + return "ld.w %1,%0\n\tld.w %R1,%R0"; + + if (GET_CODE (dst) == MEM) + return "st.w %1,%0\n\tst.w %R1,%R0"; + + return "mov %1,%0\n\tmov %R1,%R0"; +} + + +/* Return maximum offset supported for a short EP memory reference of mode + MODE and signedness UNSIGNEDP. */ + +static int +ep_memory_offset (mode, unsignedp) + enum machine_mode mode; + int ATTRIBUTE_UNUSED unsignedp; +{ + int max_offset = 0; + + switch (mode) + { + case QImode: +/* CYGNUS LOCAL v850e */ + if (TARGET_SMALL_SLD) + { + max_offset = (1 << 4); + break; + } +/* END CYGNUS LOCAL */ + max_offset = (1 << 7); + +/* CYGNUS LOCAL v850e */ +#ifdef TARGET_US_BIT_SET + if (TARGET_V850E + && ( ( unsignedp && ! TARGET_US_BIT_SET) + || (! unsignedp && TARGET_US_BIT_SET))) + max_offset = (1 << 4); +#else + if (TARGET_V850E && unsignedp) + max_offset = (1 << 4); +#endif +/* END CYGNUS LOCAL */ + break; + + case HImode: +/* CYGNUS LOCAL v850e */ + if (TARGET_SMALL_SLD) + { + max_offset = (1 << 5); + break; + } +/* END CYGNUS LOCAL v850e */ + max_offset = (1 << 8); + +/* CYGNUS LOCAL v850e */ +#ifdef TARGET_US_BIT_SET + if (TARGET_V850E + && (( unsignedp && ! TARGET_US_BIT_SET) + || (! unsignedp && TARGET_US_BIT_SET))) + max_offset = (1 << 5); +#else + if (TARGET_V850E && unsignedp) + max_offset = (1 << 5); +#endif +/* END CYGNUS LOCAL */ + break; + + case SImode: + case SFmode: + max_offset = (1 << 8); + break; + + default: + break; + } + + return max_offset; +} + +/* Return true if OP is a valid short EP memory reference */ + +int +ep_memory_operand (op, mode, unsigned_load) + rtx op; + enum machine_mode mode; + int unsigned_load; +{ + rtx addr, op0, op1; + int max_offset; + int mask; + + if (GET_CODE (op) != MEM) + return FALSE; + + max_offset = ep_memory_offset (mode, unsigned_load); + + mask = GET_MODE_SIZE (mode) - 1; + + addr = XEXP (op, 0); + if (GET_CODE (addr) == CONST) + addr = XEXP (addr, 0); + + switch (GET_CODE (addr)) + { + default: + break; + + case SYMBOL_REF: + return TDA_NAME_P (XSTR (addr, 0)); + + case REG: + return REGNO (addr) == EP_REGNUM; + + case PLUS: + op0 = XEXP (addr, 0); + op1 = XEXP (addr, 1); + if (GET_CODE (op1) == CONST_INT + && INTVAL (op1) < max_offset + && INTVAL (op1) >= 0 + && (INTVAL (op1) & mask) == 0) + { + if (GET_CODE (op0) == REG && REGNO (op0) == EP_REGNUM) + return TRUE; + + if (GET_CODE (op0) == SYMBOL_REF && TDA_NAME_P (XSTR (op0, 0))) + return TRUE; + } + break; + } + + return FALSE; +} + +/* Return true if OP is either a register or 0 */ + +int +reg_or_0_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + if (GET_CODE (op) == CONST_INT) + return INTVAL (op) == 0; + + else if (GET_CODE (op) == CONST_DOUBLE) + return CONST_DOUBLE_OK_FOR_G (op); + + else + return register_operand (op, mode); +} + +/* Return true if OP is either a register or a signed five bit integer */ + +int +reg_or_int5_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + if (GET_CODE (op) == CONST_INT) + return CONST_OK_FOR_J (INTVAL (op)); + + else + return register_operand (op, mode); +} + +/* Return true if OP is a valid call operand. */ + +int +call_address_operand (op, mode) + rtx op; + enum machine_mode ATTRIBUTE_UNUSED mode; +{ + /* Only registers are valid call operands if TARGET_LONG_CALLS. */ + if (TARGET_LONG_CALLS) + return GET_CODE (op) == REG; + return (GET_CODE (op) == SYMBOL_REF || GET_CODE (op) == REG); +} + +int +special_symbolref_operand (op, mode) + rtx op; + enum machine_mode ATTRIBUTE_UNUSED mode; +{ + if (GET_CODE (op) == SYMBOL_REF) + return ENCODED_NAME_P (XSTR (op, 0)); + + else if (GET_CODE (op) == CONST) + return (GET_CODE (XEXP (op, 0)) == PLUS + && GET_CODE (XEXP (XEXP (op, 0), 0)) == SYMBOL_REF + && ENCODED_NAME_P (XSTR (XEXP (XEXP (op, 0), 0), 0)) + && GET_CODE (XEXP (XEXP (op, 0), 1)) == CONST_INT + && CONST_OK_FOR_K (INTVAL (XEXP (XEXP (op, 0), 1)))); + + return FALSE; +} + +int +movsi_source_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + /* Some constants, as well as symbolic operands + must be done with HIGH & LO_SUM patterns. */ + if (CONSTANT_P (op) + && GET_CODE (op) != HIGH + && GET_CODE (op) != CONSTANT_P_RTX + && !(GET_CODE (op) == CONST_INT + && (CONST_OK_FOR_J (INTVAL (op)) + || CONST_OK_FOR_K (INTVAL (op)) + || CONST_OK_FOR_L (INTVAL (op))))) + return special_symbolref_operand (op, mode); + else + return general_operand (op, mode); +} + +int +power_of_two_operand (op, mode) + rtx op; + enum machine_mode ATTRIBUTE_UNUSED mode; +{ + if (GET_CODE (op) != CONST_INT) + return 0; + + if (exact_log2 (INTVAL (op)) == -1) + return 0; + return 1; +} + +int +not_power_of_two_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + unsigned int mask; + + if (mode == QImode) + mask = 0xff; + else if (mode == HImode) + mask = 0xffff; + else if (mode == SImode) + mask = 0xffffffff; + else + return 0; + + if (GET_CODE (op) != CONST_INT) + return 0; + + if (exact_log2 (~INTVAL (op) & mask) == -1) + return 0; + return 1; +} + + +/* Substitute memory references involving a pointer, to use the ep pointer, + taking care to save and preserve the ep. */ + +static void +substitute_ep_register (first_insn, last_insn, uses, regno, p_r1, p_ep) + rtx first_insn; + rtx last_insn; + int uses; + int regno; + rtx *p_r1; + rtx *p_ep; +{ + rtx reg = gen_rtx (REG, Pmode, regno); + rtx insn; + + if (!*p_r1) + { + regs_ever_live[1] = 1; + *p_r1 = gen_rtx (REG, Pmode, 1); + *p_ep = gen_rtx (REG, Pmode, 30); + } + + if (TARGET_DEBUG) + fprintf (stderr, "\ +Saved %d bytes (%d uses of register %s) in function %s, starting as insn %d, ending at %d\n", + 2 * (uses - 3), uses, reg_names[regno], + IDENTIFIER_POINTER (DECL_NAME (current_function_decl)), + INSN_UID (first_insn), INSN_UID (last_insn)); + + if (GET_CODE (first_insn) == NOTE) + first_insn = next_nonnote_insn (first_insn); + + last_insn = next_nonnote_insn (last_insn); + for (insn = first_insn; insn && insn != last_insn; insn = NEXT_INSN (insn)) + { + if (GET_CODE (insn) == INSN) + { + rtx pattern = single_set (insn); + + /* Replace the memory references. */ + if (pattern) + { + rtx *p_mem; + /* Memory operands are signed by default. */ + int unsignedp = FALSE; + + if (GET_CODE (SET_DEST (pattern)) == MEM + && GET_CODE (SET_SRC (pattern)) == MEM) + p_mem = (rtx *)0; + + else if (GET_CODE (SET_DEST (pattern)) == MEM) + p_mem = &SET_DEST (pattern); + + else if (GET_CODE (SET_SRC (pattern)) == MEM) + p_mem = &SET_SRC (pattern); + + /* CYGNUS LOCAL v850e */ + else if (GET_CODE (SET_SRC (pattern)) == SIGN_EXTEND + && GET_CODE (XEXP (SET_SRC (pattern), 0)) == MEM) + p_mem = &XEXP (SET_SRC (pattern), 0); + + else if (GET_CODE (SET_SRC (pattern)) == ZERO_EXTEND + && GET_CODE (XEXP (SET_SRC (pattern), 0)) == MEM) + { + p_mem = &XEXP (SET_SRC (pattern), 0); + unsignedp = TRUE; + } + /* END CYGNUS LOCAL */ + + else + p_mem = (rtx *)0; + + if (p_mem) + { + rtx addr = XEXP (*p_mem, 0); + + if (GET_CODE (addr) == REG && REGNO (addr) == regno) + *p_mem = change_address (*p_mem, VOIDmode, *p_ep); + + else if (GET_CODE (addr) == PLUS + && GET_CODE (XEXP (addr, 0)) == REG + && REGNO (XEXP (addr, 0)) == regno + && GET_CODE (XEXP (addr, 1)) == CONST_INT + && ((INTVAL (XEXP (addr, 1))) + < ep_memory_offset (GET_MODE (*p_mem), + unsignedp)) + && ((INTVAL (XEXP (addr, 1))) >= 0)) + *p_mem = change_address (*p_mem, VOIDmode, + gen_rtx (PLUS, Pmode, + *p_ep, XEXP (addr, 1))); + } + } + } + } + + /* Optimize back to back cases of ep <- r1 & r1 <- ep. */ + insn = prev_nonnote_insn (first_insn); + if (insn && GET_CODE (insn) == INSN + && GET_CODE (PATTERN (insn)) == SET + && SET_DEST (PATTERN (insn)) == *p_ep + && SET_SRC (PATTERN (insn)) == *p_r1) + delete_insn (insn); + else + emit_insn_before (gen_rtx (SET, Pmode, *p_r1, *p_ep), first_insn); + + emit_insn_before (gen_rtx (SET, Pmode, *p_ep, reg), first_insn); + emit_insn_before (gen_rtx (SET, Pmode, *p_ep, *p_r1), last_insn); +} + + +/* In rare cases, correct code generation requires extra machine + dependent processing between the second jump optimization pass and + delayed branch scheduling. On those machines, define this macro + as a C statement to act on the code starting at INSN. + + On the 850, we use it to implement the -mep mode to copy heavily used + pointers to ep to use the implicit addressing */ + +void v850_reorg (start_insn) + rtx start_insn; +{ + struct + { + int uses; + rtx first_insn; + rtx last_insn; + } + regs[FIRST_PSEUDO_REGISTER]; + + int i; + int use_ep = FALSE; + rtx r1 = NULL_RTX; + rtx ep = NULL_RTX; + rtx insn; + rtx pattern; + + /* If not ep mode, just return now */ + if (!TARGET_EP) + return; + + for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) + { + regs[i].uses = 0; + regs[i].first_insn = NULL_RTX; + regs[i].last_insn = NULL_RTX; + } + + for (insn = start_insn; insn != NULL_RTX; insn = NEXT_INSN (insn)) + { + switch (GET_CODE (insn)) + { + /* End of basic block */ + default: + if (!use_ep) + { + int max_uses = -1; + int max_regno = -1; + + for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) + { + if (max_uses < regs[i].uses) + { + max_uses = regs[i].uses; + max_regno = i; + } + } + + if (max_uses > 3) + substitute_ep_register (regs[max_regno].first_insn, + regs[max_regno].last_insn, + max_uses, max_regno, &r1, &ep); + } + + use_ep = FALSE; + for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) + { + regs[i].uses = 0; + regs[i].first_insn = NULL_RTX; + regs[i].last_insn = NULL_RTX; + } + break; + + case NOTE: + break; + + case INSN: + pattern = single_set (insn); + + /* See if there are any memory references we can shorten */ + if (pattern) + { + rtx src = SET_SRC (pattern); + rtx dest = SET_DEST (pattern); + rtx mem; + /* Memory operands are signed by default. */ + int unsignedp = FALSE; + + /* We might have (SUBREG (MEM)) here, so just get rid of the + subregs to make this code simpler. It is safe to call + alter_subreg any time after reload. */ + if (GET_CODE (dest) == SUBREG) + dest = alter_subreg (dest); + if (GET_CODE (src) == SUBREG) + src = alter_subreg (src); + + if (GET_CODE (dest) == MEM && GET_CODE (src) == MEM) + mem = NULL_RTX; + + else if (GET_CODE (dest) == MEM) + mem = dest; + + else if (GET_CODE (src) == MEM) + mem = src; + + /* CYGNUS LOCAL v850e */ + else if (GET_CODE (src) == SIGN_EXTEND + && GET_CODE (XEXP (src, 0)) == MEM) + mem = XEXP (src, 0); + + else if (GET_CODE (src) == ZERO_EXTEND + && GET_CODE (XEXP (src, 0)) == MEM) + { + mem = XEXP (src, 0); + unsignedp = TRUE; + } + /* END CYGNUS LOCAL */ + + else + mem = NULL_RTX; + + if (mem && ep_memory_operand (mem, GET_MODE (mem), unsignedp)) + use_ep = TRUE; + + else if (!use_ep && mem + && GET_MODE_SIZE (GET_MODE (mem)) <= UNITS_PER_WORD) + { + rtx addr = XEXP (mem, 0); + int regno = -1; + int short_p; + + if (GET_CODE (addr) == REG) + { + short_p = TRUE; + regno = REGNO (addr); + } + + else if (GET_CODE (addr) == PLUS + && GET_CODE (XEXP (addr, 0)) == REG + && GET_CODE (XEXP (addr, 1)) == CONST_INT + && ((INTVAL (XEXP (addr, 1))) + < ep_memory_offset (GET_MODE (mem), unsignedp)) + && ((INTVAL (XEXP (addr, 1))) >= 0)) + { + short_p = TRUE; + regno = REGNO (XEXP (addr, 0)); + } + + else + short_p = FALSE; + + if (short_p) + { + regs[regno].uses++; + regs[regno].last_insn = insn; + if (!regs[regno].first_insn) + regs[regno].first_insn = insn; + } + } + + /* Loading up a register in the basic block zaps any savings + for the register */ + if (GET_CODE (dest) == REG) + { + enum machine_mode mode = GET_MODE (dest); + int regno; + int endregno; + + regno = REGNO (dest); + endregno = regno + HARD_REGNO_NREGS (regno, mode); + + if (!use_ep) + { + /* See if we can use the pointer before this + modification. */ + int max_uses = -1; + int max_regno = -1; + + for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) + { + if (max_uses < regs[i].uses) + { + max_uses = regs[i].uses; + max_regno = i; + } + } + + if (max_uses > 3 + && max_regno >= regno + && max_regno < endregno) + { + substitute_ep_register (regs[max_regno].first_insn, + regs[max_regno].last_insn, + max_uses, max_regno, &r1, + &ep); + + /* Since we made a substitution, zap all remembered + registers. */ + for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) + { + regs[i].uses = 0; + regs[i].first_insn = NULL_RTX; + regs[i].last_insn = NULL_RTX; + } + } + } + + for (i = regno; i < endregno; i++) + { + regs[i].uses = 0; + regs[i].first_insn = NULL_RTX; + regs[i].last_insn = NULL_RTX; + } + } + } + } + } +} + + +/* # of registers saved by the interrupt handler. */ +#define INTERRUPT_FIXED_NUM 4 + +/* # of bytes for registers saved by the interrupt handler. */ +#define INTERRUPT_FIXED_SAVE_SIZE (4 * INTERRUPT_FIXED_NUM) + +/* # of registers saved in register parameter area. */ +#define INTERRUPT_REGPARM_NUM 4 +/* # of words saved for other registers. */ +#define INTERRUPT_ALL_SAVE_NUM \ + (30 - INTERRUPT_FIXED_NUM + INTERRUPT_REGPARM_NUM) + +#define INTERRUPT_ALL_SAVE_SIZE (4 * INTERRUPT_ALL_SAVE_NUM) + +int +compute_register_save_size (p_reg_saved) + long *p_reg_saved; +{ + int size = 0; + int i; + int interrupt_handler = v850_interrupt_function_p (current_function_decl); + int call_p = regs_ever_live [LINK_POINTER_REGNUM]; + long reg_saved = 0; + + /* Count the return pointer if we need to save it. */ + if (profile_flag && !call_p) + regs_ever_live [LINK_POINTER_REGNUM] = call_p = 1; + + /* Count space for the register saves. */ + if (interrupt_handler) + { + for (i = 0; i <= 31; i++) + switch (i) + { + default: + if (regs_ever_live[i] || call_p) + { + size += 4; + reg_saved |= 1L << i; + } + break; + + /* We don't save/restore r0 or the stack pointer */ + case 0: + case STACK_POINTER_REGNUM: + break; + + /* For registers with fixed use, we save them, set them to the + appropriate value, and then restore them. + These registers are handled specially, so don't list them + on the list of registers to save in the prologue. */ + case 1: /* temp used to hold ep */ + case 4: /* gp */ + case 10: /* temp used to call interrupt save/restore */ + case EP_REGNUM: /* ep */ + size += 4; + break; + } + } + else + { + /* Find the first register that needs to be saved. */ + for (i = 0; i <= 31; i++) + if (regs_ever_live[i] && ((! call_used_regs[i]) + || i == LINK_POINTER_REGNUM)) + break; + + /* If it is possible that an out-of-line helper function might be + used to generate the prologue for the current function, then we + need to cover the possibility that such a helper function will + be used, despite the fact that there might be gaps in the list of + registers that need to be saved. To detect this we note that the + helper functions always push at least register r29 if the link + register is not used, and at least registers r27 - r31 if the + link register is used (and provided that the function is not an + interrupt handler). */ + + if (TARGET_PROLOG_FUNCTION + && (i == 2 || i >= 20) + && regs_ever_live[LINK_POINTER_REGNUM] ? (i < 28) : (i < 30)) + { + if (i == 2) + { + size += 4; + reg_saved |= 1L << i; + + i = 20; + } + + /* Helper functions save all registers between the starting + register and the last register, regardless of whether they + are actually used by the function or not. */ + for (; i <= 29; i++) + { + size += 4; + reg_saved |= 1L << i; + } + + if (regs_ever_live [LINK_POINTER_REGNUM]) + { + size += 4; + reg_saved |= 1L << LINK_POINTER_REGNUM; + } + } + else + { + for (; i <= 31; i++) + if (regs_ever_live[i] && ((! call_used_regs[i]) + || i == LINK_POINTER_REGNUM)) + { + size += 4; + reg_saved |= 1L << i; + } + } + } + + if (p_reg_saved) + *p_reg_saved = reg_saved; + + return size; +} + +int +compute_frame_size (size, p_reg_saved) + int size; + long *p_reg_saved; +{ + extern int current_function_outgoing_args_size; + + return (size + + compute_register_save_size (p_reg_saved) + + current_function_outgoing_args_size); +} + + +void +expand_prologue () +{ + unsigned int i; + int offset; + unsigned int size = get_frame_size (); + unsigned int actual_fsize; + unsigned int init_stack_alloc = 0; + rtx save_regs[32]; + rtx save_all; + unsigned int num_save; + unsigned int default_stack; + int code; + int interrupt_handler = v850_interrupt_function_p (current_function_decl); + long reg_saved = 0; + + actual_fsize = compute_frame_size (size, ®_saved); + + /* Save/setup global registers for interrupt functions right now */ + if (interrupt_handler) + { +/* CYGNUS LOCAL v850e */ + if (TARGET_V850E && ! TARGET_DISABLE_CALLT) + emit_insn (gen_save_interrupt_v850e ()); + else +/* END CYGNUS LOCAL */ + emit_insn (gen_save_interrupt ()); + + actual_fsize -= INTERRUPT_FIXED_SAVE_SIZE; + + if (((1L << LINK_POINTER_REGNUM) & reg_saved) != 0) + actual_fsize -= INTERRUPT_ALL_SAVE_SIZE; + } + + /* Save arg registers to the stack if necessary. */ + else if (current_function_anonymous_args) + { + if (TARGET_PROLOG_FUNCTION) + { +/* CYGNUS LOCAL v850e */ + if (TARGET_V850E && ! TARGET_DISABLE_CALLT) + emit_insn (gen_save_r6_r9_v850e ()); + else +/* END CYGNUS LOCAL */ + emit_insn (gen_save_r6_r9 ()); + } + else + { + offset = 0; + for (i = 6; i < 10; i++) + { + emit_move_insn (gen_rtx (MEM, SImode, + plus_constant (stack_pointer_rtx, + offset)), + gen_rtx (REG, SImode, i)); + offset += 4; + } + } + } + + /* Identify all of the saved registers */ + num_save = 0; + default_stack = 0; + for (i = 1; i < 31; i++) + { + if (((1L << i) & reg_saved) != 0) + save_regs[num_save++] = gen_rtx (REG, Pmode, i); + } + + /* If the return pointer is saved, the helper functions also allocate + 16 bytes of stack for arguments to be saved in. */ + if (((1L << LINK_POINTER_REGNUM) & reg_saved) != 0) + { + save_regs[num_save++] = gen_rtx (REG, Pmode, LINK_POINTER_REGNUM); + default_stack = 16; + } + + /* See if we have an insn that allocates stack space and saves the particular + registers we want to. */ + save_all = NULL_RTX; + if (TARGET_PROLOG_FUNCTION && num_save > 0 && actual_fsize >= default_stack) + { + int alloc_stack = (4 * num_save) + default_stack; + int unalloc_stack = actual_fsize - alloc_stack; + int save_func_len = 4; + int save_normal_len; + + if (unalloc_stack) + save_func_len += CONST_OK_FOR_J (unalloc_stack) ? 2 : 4; + + /* see if we would have used ep to save the stack */ + if (TARGET_EP && num_save > 3 && (unsigned)actual_fsize < 255) + save_normal_len = (3 * 2) + (2 * num_save); + else + save_normal_len = 4 * num_save; + + save_normal_len += CONST_OK_FOR_J (actual_fsize) ? 2 : 4; + + /* Don't bother checking if we don't actually save any space. + This happens for instance if one register is saved and additional + stack space is allocated. */ + if (save_func_len < save_normal_len) + { + save_all = gen_rtx (PARALLEL, VOIDmode, + rtvec_alloc (num_save + (TARGET_V850 ? 2 : 1))); + XVECEXP (save_all, 0, 0) = gen_rtx (SET, VOIDmode, + stack_pointer_rtx, + gen_rtx (PLUS, Pmode, + stack_pointer_rtx, + GEN_INT (-alloc_stack))); + + if (TARGET_V850) + { + XVECEXP (save_all, 0, num_save + 1) + = gen_rtx (CLOBBER, VOIDmode, gen_rtx (REG, Pmode, 10)); + } + + offset = - default_stack; + for (i = 0; i < num_save; i++) + { + XVECEXP (save_all, 0, i + 1) + = gen_rtx (SET, VOIDmode, + gen_rtx (MEM, Pmode, + plus_constant (stack_pointer_rtx, offset)), + save_regs[i]); + offset -= 4; + } + + code = recog (save_all, NULL_RTX, NULL_PTR); + if (code >= 0) + { + rtx insn = emit_insn (save_all); + INSN_CODE (insn) = code; + actual_fsize -= alloc_stack; + + if (TARGET_DEBUG) + fprintf (stderr, "\ +Saved %d bytes via prologue function (%d vs. %d) for function %s\n", + save_normal_len - save_func_len, + save_normal_len, save_func_len, + IDENTIFIER_POINTER (DECL_NAME (current_function_decl))); + } + else + save_all = NULL_RTX; + } + } + + /* If no prolog save function is available, store the registers the old + fashioned way (one by one). */ + if (!save_all) + { + /* Special case interrupt functions that save all registers for a call. */ + if (interrupt_handler && ((1L << LINK_POINTER_REGNUM) & reg_saved) != 0) + { +/* CYGNUS LOCAL v850e */ + if (TARGET_V850E && ! TARGET_DISABLE_CALLT) + emit_insn (gen_save_all_interrupt_v850e ()); + else +/* END CYGNUS LOCAL */ + emit_insn (gen_save_all_interrupt ()); + } + else + { + /* If the stack is too big, allocate it in chunks so we can do the + register saves. We use the register save size so we use the ep + register. */ + if (actual_fsize && !CONST_OK_FOR_K (-actual_fsize)) + init_stack_alloc = compute_register_save_size (NULL); + else + init_stack_alloc = actual_fsize; + + /* Save registers at the beginning of the stack frame */ + offset = init_stack_alloc - 4; + + if (init_stack_alloc) + emit_insn (gen_addsi3 (stack_pointer_rtx, + stack_pointer_rtx, + GEN_INT (-init_stack_alloc))); + + /* Save the return pointer first. */ + if (num_save > 0 && REGNO (save_regs[num_save-1]) == LINK_POINTER_REGNUM) + { + emit_move_insn (gen_rtx (MEM, SImode, + plus_constant (stack_pointer_rtx, + offset)), + save_regs[--num_save]); + offset -= 4; + } + + for (i = 0; i < num_save; i++) + { + emit_move_insn (gen_rtx (MEM, SImode, + plus_constant (stack_pointer_rtx, + offset)), + save_regs[i]); + offset -= 4; + } + } + } + + /* Allocate the rest of the stack that was not allocated above (either it is + > 32K or we just called a function to save the registers and needed more + stack. */ + if (actual_fsize > init_stack_alloc) + { + int diff = actual_fsize - init_stack_alloc; + if (CONST_OK_FOR_K (diff)) + emit_insn (gen_addsi3 (stack_pointer_rtx, + stack_pointer_rtx, + GEN_INT (-diff))); + else + { + rtx reg = gen_rtx (REG, Pmode, 12); + emit_move_insn (reg, GEN_INT (-diff)); + emit_insn (gen_addsi3 (stack_pointer_rtx, stack_pointer_rtx, reg)); + } + } + + /* If we need a frame pointer, set it up now. */ + if (frame_pointer_needed) + emit_move_insn (hard_frame_pointer_rtx, stack_pointer_rtx); +} + + +void +expand_epilogue () +{ + unsigned int i; + int offset; + unsigned int size = get_frame_size (); + long reg_saved = 0; + unsigned int actual_fsize = compute_frame_size (size, ®_saved); + unsigned int init_stack_free = 0; + rtx restore_regs[32]; + rtx restore_all; + unsigned int num_restore; + unsigned int default_stack; + int code; + int interrupt_handler = v850_interrupt_function_p (current_function_decl); + + /* Eliminate the initial stack stored by interrupt functions. */ + if (interrupt_handler) + { + actual_fsize -= INTERRUPT_FIXED_SAVE_SIZE; + if (((1L << LINK_POINTER_REGNUM) & reg_saved) != 0) + actual_fsize -= INTERRUPT_ALL_SAVE_SIZE; + } + + /* Cut off any dynamic stack created. */ + if (frame_pointer_needed) + emit_move_insn (stack_pointer_rtx, hard_frame_pointer_rtx); + + /* Identify all of the saved registers */ + num_restore = 0; + default_stack = 0; + for (i = 1; i < 31; i++) + { + if (((1L << i) & reg_saved) != 0) + restore_regs[num_restore++] = gen_rtx (REG, Pmode, i); + } + + /* If the return pointer is saved, the helper functions also allocate + 16 bytes of stack for arguments to be saved in. */ + if (((1L << LINK_POINTER_REGNUM) & reg_saved) != 0) + { + restore_regs[num_restore++] = gen_rtx (REG, Pmode, LINK_POINTER_REGNUM); + default_stack = 16; + } + + /* See if we have an insn that restores the particular registers we + want to. */ + restore_all = NULL_RTX; + if (TARGET_PROLOG_FUNCTION && num_restore > 0 + && actual_fsize >= default_stack + && !interrupt_handler) + { + int alloc_stack = (4 * num_restore) + default_stack; + int unalloc_stack = actual_fsize - alloc_stack; + int restore_func_len = 4; + int restore_normal_len; + + if (unalloc_stack) + restore_func_len += CONST_OK_FOR_J (unalloc_stack) ? 2 : 4; + + /* see if we would have used ep to restore the registers */ + if (TARGET_EP && num_restore > 3 && (unsigned)actual_fsize < 255) + restore_normal_len = (3 * 2) + (2 * num_restore); + else + restore_normal_len = 4 * num_restore; + + restore_normal_len += (CONST_OK_FOR_J (actual_fsize) ? 2 : 4) + 2; + + /* Don't bother checking if we don't actually save any space. */ + if (restore_func_len < restore_normal_len) + { + restore_all = gen_rtx (PARALLEL, VOIDmode, + rtvec_alloc (num_restore + 2)); + XVECEXP (restore_all, 0, 0) = gen_rtx (RETURN, VOIDmode); + XVECEXP (restore_all, 0, 1) + = gen_rtx (SET, VOIDmode, stack_pointer_rtx, + gen_rtx (PLUS, Pmode, + stack_pointer_rtx, + GEN_INT (alloc_stack))); + + offset = alloc_stack - 4; + for (i = 0; i < num_restore; i++) + { + XVECEXP (restore_all, 0, i+2) + = gen_rtx (SET, VOIDmode, + restore_regs[i], + gen_rtx (MEM, Pmode, + plus_constant + (stack_pointer_rtx, offset))); + offset -= 4; + } + + code = recog (restore_all, NULL_RTX, NULL_PTR); + if (code >= 0) + { + rtx insn; + + actual_fsize -= alloc_stack; + if (actual_fsize) + { + if (CONST_OK_FOR_K (actual_fsize)) + emit_insn (gen_addsi3 (stack_pointer_rtx, + stack_pointer_rtx, + GEN_INT (actual_fsize))); + else + { + rtx reg = gen_rtx (REG, Pmode, 12); + emit_move_insn (reg, GEN_INT (actual_fsize)); + emit_insn (gen_addsi3 (stack_pointer_rtx, + stack_pointer_rtx, + reg)); + } + } + + insn = emit_jump_insn (restore_all); + INSN_CODE (insn) = code; + + if (TARGET_DEBUG) + fprintf (stderr, "\ +Saved %d bytes via epilogue function (%d vs. %d) in function %s\n", + restore_normal_len - restore_func_len, + restore_normal_len, restore_func_len, + IDENTIFIER_POINTER (DECL_NAME (current_function_decl))); + } + else + restore_all = NULL_RTX; + } + } + + /* If no epilog save function is available, restore the registers the + old fashioned way (one by one). */ + if (!restore_all) + { + /* If the stack is large, we need to cut it down in 2 pieces. */ + if (actual_fsize && !CONST_OK_FOR_K (-actual_fsize)) + init_stack_free = 4 * num_restore; + else + init_stack_free = actual_fsize; + + /* Deallocate the rest of the stack if it is > 32K or if extra stack + was allocated for an interrupt handler that makes a call. */ + if (actual_fsize > init_stack_free + || (interrupt_handler && actual_fsize)) + { + int diff; + + diff = actual_fsize - ((interrupt_handler) ? 0 : init_stack_free); + + if (CONST_OK_FOR_K (diff)) + emit_insn (gen_addsi3 (stack_pointer_rtx, + stack_pointer_rtx, + GEN_INT (diff))); + else + { + rtx reg = gen_rtx (REG, Pmode, 12); + emit_move_insn (reg, GEN_INT (diff)); + emit_insn (gen_addsi3 (stack_pointer_rtx, + stack_pointer_rtx, + reg)); + } + } + + /* Special case interrupt functions that save all registers + for a call. */ + if (interrupt_handler && ((1L << LINK_POINTER_REGNUM) & reg_saved) != 0) + { +/* CYGNUS LOCAL v850e */ + if (TARGET_V850E && ! TARGET_DISABLE_CALLT) + emit_insn (gen_restore_all_interrupt_v850e ()); + else +/* END CYGNUS LOCAL */ + emit_insn (gen_restore_all_interrupt ()); + } + else + { + /* Restore registers from the beginning of the stack frame */ + offset = init_stack_free - 4; + + /* Restore the return pointer first. */ + if (num_restore > 0 + && REGNO (restore_regs [num_restore - 1]) == LINK_POINTER_REGNUM) + { + emit_move_insn (restore_regs[--num_restore], + gen_rtx (MEM, SImode, + plus_constant (stack_pointer_rtx, + offset))); + offset -= 4; + } + + for (i = 0; i < num_restore; i++) + { + emit_move_insn (restore_regs[i], + gen_rtx (MEM, SImode, + plus_constant (stack_pointer_rtx, + offset))); + + offset -= 4; + } + + /* Cut back the remainder of the stack. */ + if (init_stack_free) + emit_insn (gen_addsi3 (stack_pointer_rtx, + stack_pointer_rtx, + GEN_INT (init_stack_free))); + } + + /* And return or use reti for interrupt handlers. */ + if (interrupt_handler) + emit_jump_insn (gen_restore_interrupt ()); + else if (actual_fsize) + emit_jump_insn (gen_return_internal ()); + else + emit_jump_insn (gen_return ()); + } + + current_function_anonymous_args = 0; + v850_interrupt_cache_p = FALSE; + v850_interrupt_p = FALSE; +} + + +/* Update the condition code from the insn. */ + +void +notice_update_cc (body, insn) + rtx body; + rtx insn; +{ + switch (get_attr_cc (insn)) + { + case CC_NONE: + /* Insn does not affect CC at all. */ + break; + + case CC_NONE_0HIT: + /* Insn does not change CC, but the 0'th operand has been changed. */ + if (cc_status.value1 != 0 + && reg_overlap_mentioned_p (recog_operand[0], cc_status.value1)) + cc_status.value1 = 0; + break; + + case CC_SET_ZN: + /* Insn sets the Z,N flags of CC to recog_operand[0]. + V,C is in an unusable state. */ + CC_STATUS_INIT; + cc_status.flags |= CC_OVERFLOW_UNUSABLE | CC_NO_CARRY; + cc_status.value1 = recog_operand[0]; + break; + + case CC_SET_ZNV: + /* Insn sets the Z,N,V flags of CC to recog_operand[0]. + C is in an unusable state. */ + CC_STATUS_INIT; + cc_status.flags |= CC_NO_CARRY; + cc_status.value1 = recog_operand[0]; + break; + + case CC_COMPARE: + /* The insn is a compare instruction. */ + CC_STATUS_INIT; + cc_status.value1 = SET_SRC (body); + break; + + case CC_CLOBBER: + /* Insn doesn't leave CC in a usable state. */ + CC_STATUS_INIT; + break; + } +} + +/* Retrieve the data area that has been chosen for the given decl. */ + +v850_data_area +v850_get_data_area (decl) + tree decl; +{ + if (lookup_attribute ("sda", DECL_MACHINE_ATTRIBUTES (decl)) != NULL_TREE) + return DATA_AREA_SDA; + + if (lookup_attribute ("tda", DECL_MACHINE_ATTRIBUTES (decl)) != NULL_TREE) + return DATA_AREA_TDA; + + if (lookup_attribute ("zda", DECL_MACHINE_ATTRIBUTES (decl)) != NULL_TREE) + return DATA_AREA_ZDA; + + return DATA_AREA_NORMAL; +} + +/* Store the indicated data area in the decl's attributes. */ + +static void +v850_set_data_area (decl, data_area) + tree decl; + v850_data_area data_area; +{ + tree name; + + switch (data_area) + { + case DATA_AREA_SDA: name = get_identifier ("sda"); break; + case DATA_AREA_TDA: name = get_identifier ("tda"); break; + case DATA_AREA_ZDA: name = get_identifier ("zda"); break; + default: + return; + } + + DECL_MACHINE_ATTRIBUTES (decl) = tree_cons + (name, NULL, DECL_MACHINE_ATTRIBUTES (decl)); +} + +/* Return nonzero if ATTR is a valid attribute for DECL. + ARGS are the arguments supplied with ATTR. */ + +int +v850_valid_machine_decl_attribute (decl, attr, args) + tree decl; + tree attr; + tree args; +{ + v850_data_area data_area; + v850_data_area area; + + if (args != NULL_TREE) + return 0; + + if (is_attribute_p ("interrupt_handler", attr) + || is_attribute_p ("interrupt", attr)) + return TREE_CODE (decl) == FUNCTION_DECL; + + /* Implement data area attribute. */ + if (is_attribute_p ("sda", attr)) + data_area = DATA_AREA_SDA; + else if (is_attribute_p ("tda", attr)) + data_area = DATA_AREA_TDA; + else if (is_attribute_p ("zda", attr)) + data_area = DATA_AREA_ZDA; + else + return 0; + + switch (TREE_CODE (decl)) + { + case VAR_DECL: + if (current_function_decl != NULL_TREE) + error_with_decl (decl, "\ +a data area attribute cannot be specified for local variables"); + + /* Drop through. */ + + case FUNCTION_DECL: + area = v850_get_data_area (decl); + if (area != DATA_AREA_NORMAL && data_area != area) + error_with_decl (decl, "\ +data area of '%s' conflicts with previous declaration"); + + return 1; + + default: + break; + } + + return 0; +} + + +/* Return nonzero if FUNC is an interrupt function as specified + by the "interrupt" attribute. */ + +int +v850_interrupt_function_p (func) + tree func; +{ + tree a; + int ret = 0; + + if (v850_interrupt_cache_p) + return v850_interrupt_p; + + if (TREE_CODE (func) != FUNCTION_DECL) + return 0; + + a = lookup_attribute ("interrupt_handler", DECL_MACHINE_ATTRIBUTES (func)); + if (a != NULL_TREE) + ret = 1; + + else + { + a = lookup_attribute ("interrupt", DECL_MACHINE_ATTRIBUTES (func)); + ret = a != NULL_TREE; + } + + /* Its not safe to trust global variables until after function inlining has + been done. */ + if (reload_completed | reload_in_progress) + v850_interrupt_p = ret; + + return ret; +} + + +extern struct obstack * saveable_obstack; + +void +v850_encode_data_area (decl) + tree decl; +{ + char * str = XSTR (XEXP (DECL_RTL (decl), 0), 0); + int len = strlen (str); + char * newstr; + + /* Map explict sections into the appropriate attribute */ + if (v850_get_data_area (decl) == DATA_AREA_NORMAL) + { + if (DECL_SECTION_NAME (decl)) + { + char * name = TREE_STRING_POINTER (DECL_SECTION_NAME (decl)); + + if (streq (name, ".zdata") || streq (name, ".zbss")) + v850_set_data_area (decl, DATA_AREA_ZDA); + + else if (streq (name, ".sdata") || streq (name, ".sbss")) + v850_set_data_area (decl, DATA_AREA_SDA); + + else if (streq (name, ".tdata")) + v850_set_data_area (decl, DATA_AREA_TDA); + } + + /* If no attribute, support -m{zda,sda,tda}=n */ + else + { + int size = int_size_in_bytes (TREE_TYPE (decl)); + if (size <= 0) + ; + + else if (size <= small_memory [(int) SMALL_MEMORY_TDA].max) + v850_set_data_area (decl, DATA_AREA_TDA); + + else if (size <= small_memory [(int) SMALL_MEMORY_SDA].max) + v850_set_data_area (decl, DATA_AREA_SDA); + + else if (size <= small_memory [(int) SMALL_MEMORY_ZDA].max) + v850_set_data_area (decl, DATA_AREA_ZDA); + } + + if (v850_get_data_area (decl) == DATA_AREA_NORMAL) + return; + } + + newstr = obstack_alloc (saveable_obstack, len + 2); + + strcpy (newstr + 1, str); + + switch (v850_get_data_area (decl)) + { + case DATA_AREA_ZDA: *newstr = ZDA_NAME_FLAG_CHAR; break; + case DATA_AREA_TDA: *newstr = TDA_NAME_FLAG_CHAR; break; + case DATA_AREA_SDA: *newstr = SDA_NAME_FLAG_CHAR; break; + default: abort (); + } + + XSTR (XEXP (DECL_RTL (decl), 0), 0) = newstr; +} + +/* Return true if the given RTX is a register which can be restored + by a function epilogue. */ +int +register_is_ok_for_epilogue (op, mode) + rtx op; + enum machine_mode ATTRIBUTE_UNUSED mode; +{ + /* The save/restore routines can only cope with registers 2, and 20 - 31 */ + return (GET_CODE (op) == REG) + && (((REGNO (op) >= 20) && REGNO (op) <= 31) + || REGNO (op) == 2); +} + +/* Return non-zero if the given RTX is suitable for collapsing into + jump to a function epilogue. */ +int +pattern_is_ok_for_epilogue (op, mode) + rtx op; + enum machine_mode ATTRIBUTE_UNUSED mode; +{ + int count = XVECLEN (op, 0); + int i; + + /* If there are no registers to restore then the function epilogue + is not suitable. */ + if (count <= 2) + return 0; + + /* The pattern matching has already established that we are performing a + function epilogue and that we are popping at least one register. We must + now check the remaining entries in the vector to make sure that they are + also register pops. There is no good reason why there should ever be + anything else in this vector, but being paranoid always helps... + + The test below performs the C equivalent of this machine description + pattern match: + + (set (match_operand:SI n "register_is_ok_for_epilogue" "r") + (mem:SI (plus:SI (reg:SI 3) (match_operand:SI n "immediate_operand" "i")))) + */ + + for (i = 3; i < count; i++) + { + rtx vector_element = XVECEXP (op, 0, i); + rtx dest; + rtx src; + rtx plus; + + if (GET_CODE (vector_element) != SET) + return 0; + + dest = SET_DEST (vector_element); + src = SET_SRC (vector_element); + + if (GET_CODE (dest) != REG + || GET_MODE (dest) != SImode + || ! register_is_ok_for_epilogue (dest, SImode) + || GET_CODE (src) != MEM + || GET_MODE (src) != SImode) + return 0; + + plus = XEXP (src, 0); + + if (GET_CODE (plus) != PLUS + || GET_CODE (XEXP (plus, 0)) != REG + || GET_MODE (XEXP (plus, 0)) != SImode + || REGNO (XEXP (plus, 0)) != STACK_POINTER_REGNUM + || GET_CODE (XEXP (plus, 1)) != CONST_INT) + return 0; + } + + return 1; +} + +/* Construct a JR instruction to a routine that will perform the equivalent of + the RTL passed in as an argument. This RTL is a function epilogue that + pops registers off the stack and possibly releases some extra stack space + as well. The code has already verified that the RTL matches these + requirements. */ +char * +construct_restore_jr (op) + rtx op; +{ + int count = XVECLEN (op, 0); + int stack_bytes; + unsigned long int mask; + unsigned long int first; + unsigned long int last; + int i; + static char buff [100]; /* XXX */ + + if (count <= 2) + { + error ("Bogus JR construction: %d\n", count); + return NULL; + } + + /* Work out how many bytes to pop off the stack before retrieving + registers. */ + if (GET_CODE (XVECEXP (op, 0, 1)) != SET) + abort (); + if (GET_CODE (SET_SRC (XVECEXP (op, 0, 1))) != PLUS) + abort (); + if (GET_CODE (XEXP (SET_SRC (XVECEXP (op, 0, 1)), 1)) != CONST_INT) + abort (); + + stack_bytes = INTVAL (XEXP (SET_SRC (XVECEXP (op, 0, 1)), 1)); + + /* Each pop will remove 4 bytes from the stack... */ + stack_bytes -= (count - 2) * 4; + + /* Make sure that the amount we are popping either 0 or 16 bytes. */ + if (stack_bytes != 0 && stack_bytes != 16) + { + error ("Bad amount of stack space removal: %d", stack_bytes); + return NULL; + } + + /* Now compute the bit mask of registers to push. */ + mask = 0; + for (i = 2; i < count; i++) + { + rtx vector_element = XVECEXP (op, 0, i); + + if (GET_CODE (vector_element) != SET) + abort (); + if (GET_CODE (SET_DEST (vector_element)) != REG) + abort (); + if (! register_is_ok_for_epilogue (SET_DEST (vector_element), SImode)) + abort (); + + mask |= 1 << REGNO (SET_DEST (vector_element)); + } + + /* Scan for the first register to pop. */ + for (first = 0; first < 32; first++) + { + if (mask & (1 << first)) + break; + } + + if (first >= 32) + abort (); + + /* Discover the last register to pop. */ + if (mask & (1 << LINK_POINTER_REGNUM)) + { + if (stack_bytes != 16) + abort (); + + last = LINK_POINTER_REGNUM; + } + else + { + if (stack_bytes != 0) + abort (); + + if ((mask & (1 << 29)) == 0) + abort (); + + last = 29; + } + + /* Note, it is possible to have gaps in the register mask. + We ignore this here, and generate a JR anyway. We will + be popping more registers than is strictly necessary, but + it does save code space. */ + + if (TARGET_LONG_CALLS) + { + char name[40]; + + if (first == last) + sprintf (name, "__return_%s", reg_names [first]); + else + sprintf (name, "__return_%s_%s", reg_names [first], reg_names [last]); + + sprintf (buff, "movhi hi(%s), r0, r6\n\tmovea lo(%s), r6, r6\n\tjmp r6", + name, name); + } + else + { + if (first == last) + sprintf (buff, "jr __return_%s", reg_names [first]); + else + sprintf (buff, "jr __return_%s_%s", reg_names [first], reg_names [last]); + } + + return buff; +} + + +/* Return non-zero if the given RTX is suitable for collapsing into + a jump to a function prologue. */ +int +pattern_is_ok_for_prologue (op, mode) + rtx op; + enum machine_mode ATTRIBUTE_UNUSED mode; +{ + int count = XVECLEN (op, 0); + int i; + rtx vector_element; + + /* If there are no registers to save then the function prologue + is not suitable. */ + if (count <= 2) + return 0; + + /* The pattern matching has already established that we are adjusting the + stack and pushing at least one register. We must now check that the + remaining entries in the vector to make sure that they are also register + pushes, except for the last entry which should be a CLOBBER of r10. + + The test below performs the C equivalent of this machine description + pattern match: + + (set (mem:SI (plus:SI (reg:SI 3) + (match_operand:SI 2 "immediate_operand" "i"))) + (match_operand:SI 3 "register_is_ok_for_epilogue" "r")) + + */ + + for (i = 2; i < count - 1; i++) + { + rtx dest; + rtx src; + rtx plus; + + vector_element = XVECEXP (op, 0, i); + + if (GET_CODE (vector_element) != SET) + return 0; + + dest = SET_DEST (vector_element); + src = SET_SRC (vector_element); + + if (GET_CODE (dest) != MEM + || GET_MODE (dest) != SImode + || GET_CODE (src) != REG + || GET_MODE (src) != SImode + || ! register_is_ok_for_epilogue (src, SImode)) + return 0; + + plus = XEXP (dest, 0); + + if ( GET_CODE (plus) != PLUS + || GET_CODE (XEXP (plus, 0)) != REG + || GET_MODE (XEXP (plus, 0)) != SImode + || REGNO (XEXP (plus, 0)) != STACK_POINTER_REGNUM + || GET_CODE (XEXP (plus, 1)) != CONST_INT) + return 0; + + /* If the register is being pushed somewhere other than the stack + space just acquired by the first operand then abandon this quest. + Note: the test is <= because both values are negative. */ + if (INTVAL (XEXP (plus, 1)) + <= INTVAL (XEXP (SET_SRC (XVECEXP (op, 0, 0)), 1))) + { + return 0; + } + } + + /* Make sure that the last entry in the vector is a clobber. */ + vector_element = XVECEXP (op, 0, i); + + if (GET_CODE (vector_element) != CLOBBER + || GET_CODE (XEXP (vector_element, 0)) != REG + || REGNO (XEXP (vector_element, 0)) != 10) + return 0; + + return 1; +} + +/* Construct a JARL instruction to a routine that will perform the equivalent + of the RTL passed as a parameter. This RTL is a function prologue that + saves some of the registers r20 - r31 onto the stack, and possibly acquires + some stack space as well. The code has already verified that the RTL + matches these requirements. */ +char * +construct_save_jarl (op) + rtx op; +{ + int count = XVECLEN (op, 0); + int stack_bytes; + unsigned long int mask; + unsigned long int first; + unsigned long int last; + int i; + static char buff [100]; /* XXX */ + + if (count <= 2) + { + error ("Bogus JARL construction: %d\n", count); + return NULL; + } + + /* Paranoia. */ + if (GET_CODE (XVECEXP (op, 0, 0)) != SET) + abort (); + if (GET_CODE (SET_SRC (XVECEXP (op, 0, 0))) != PLUS) + abort (); + if (GET_CODE (XEXP (SET_SRC (XVECEXP (op, 0, 0)), 0)) != REG) + abort (); + if (GET_CODE (XEXP (SET_SRC (XVECEXP (op, 0, 0)), 1)) != CONST_INT) + abort (); + + /* Work out how many bytes to push onto the stack after storing the + registers. */ + stack_bytes = INTVAL (XEXP (SET_SRC (XVECEXP (op, 0, 0)), 1)); + + /* Each push will put 4 bytes from the stack... */ + stack_bytes += (count - 2) * 4; + + /* Make sure that the amount we are popping either 0 or 16 bytes. */ + if (stack_bytes != 0 && stack_bytes != -16) + { + error ("Bad amount of stack space removal: %d", stack_bytes); + return NULL; + } + + /* Now compute the bit mask of registers to push. */ + mask = 0; + for (i = 1; i < count - 1; i++) + { + rtx vector_element = XVECEXP (op, 0, i); + + if (GET_CODE (vector_element) != SET) + abort (); + if (GET_CODE (SET_SRC (vector_element)) != REG) + abort (); + if (! register_is_ok_for_epilogue (SET_SRC (vector_element), SImode)) + abort (); + + mask |= 1 << REGNO (SET_SRC (vector_element)); + } + + /* Scan for the first register to push. */ + for (first = 0; first < 32; first++) + { + if (mask & (1 << first)) + break; + } + + if (first >= 32) + abort (); + + /* Discover the last register to push. */ + if (mask & (1 << LINK_POINTER_REGNUM)) + { + if (stack_bytes != -16) + abort (); + + last = LINK_POINTER_REGNUM; + } + else + { + if (stack_bytes != 0) + abort (); + if ((mask & (1 << 29)) == 0) + abort (); + + last = 29; + } + + /* Note, it is possible to have gaps in the register mask. + We ignore this here, and generate a JARL anyway. We will + be pushing more registers than is strictly necessary, but + it does save code space. */ + + if (TARGET_LONG_CALLS) + { + char name[40]; + + if (first == last) + sprintf (name, "__save_%s", reg_names [first]); + else + sprintf (name, "__save_%s_%s", reg_names [first], reg_names [last]); + + sprintf (buff, "movhi hi(%s), r0, r11\n\tmovea lo(%s), r11, r11\n\tjarl .+4, r10\n\tadd 4, r10\n\tjmp r11", + name, name); + } + else + { + if (first == last) + sprintf (buff, "jarl __save_%s, r10", reg_names [first]); + else + sprintf (buff, "jarl __save_%s_%s, r10", reg_names [first], + reg_names [last]); + } + + return buff; +} + +extern tree last_assemble_variable_decl; +extern int size_directive_output; + +/* A version of asm_output_aligned_bss() that copes with the special + data areas of the v850. */ +void +v850_output_aligned_bss (file, decl, name, size, align) + FILE * file; + tree decl; + char * name; + int size; + int align; +{ + ASM_GLOBALIZE_LABEL (file, name); + + switch (v850_get_data_area (decl)) + { + case DATA_AREA_ZDA: + zbss_section (); + break; + + case DATA_AREA_SDA: + sbss_section (); + break; + + case DATA_AREA_TDA: + tdata_section (); + + default: + bss_section (); + break; + } + + ASM_OUTPUT_ALIGN (file, floor_log2 (align / BITS_PER_UNIT)); +#ifdef ASM_DECLARE_OBJECT_NAME + last_assemble_variable_decl = decl; + ASM_DECLARE_OBJECT_NAME (file, name, decl); +#else + /* Standard thing is just output label for the object. */ + ASM_OUTPUT_LABEL (file, name); +#endif /* ASM_DECLARE_OBJECT_NAME */ + ASM_OUTPUT_SKIP (file, size ? size : 1); +} + +/* Called via the macro ASM_OUTPUT_DECL_COMMON */ +void +v850_output_common (file, decl, name, size, align) + FILE * file; + tree decl; + char * name; + int size; + int align; +{ + if (decl == NULL_TREE) + { + fprintf (file, "\t%s\t", COMMON_ASM_OP); + } + else + { + switch (v850_get_data_area (decl)) + { + case DATA_AREA_ZDA: + fprintf (file, "\t%s\t", ZCOMMON_ASM_OP); + break; + + case DATA_AREA_SDA: + fprintf (file, "\t%s\t", SCOMMON_ASM_OP); + break; + + case DATA_AREA_TDA: + fprintf (file, "\t%s\t", TCOMMON_ASM_OP); + break; + + default: + fprintf (file, "\t%s\t", COMMON_ASM_OP); + break; + } + } + + assemble_name (file, name); + fprintf (file, ",%u,%u\n", size, align / BITS_PER_UNIT); +} + +/* Called via the macro ASM_OUTPUT_DECL_LOCAL */ +void +v850_output_local (file, decl, name, size, align) + FILE * file; + tree decl; + char * name; + int size; + int align; +{ + fprintf (file, "\t%s\t", LOCAL_ASM_OP); + assemble_name (file, name); + fprintf (file, "\n"); + + ASM_OUTPUT_ALIGNED_DECL_COMMON (file, decl, name, size, align); +} + +/* The following code is for handling pragmas supported by the + v850 compiler produced by Green Hills Software. This is at + the specific request of a customer. */ + +/* Track the current data area set by the data area pragma (which + can be nested). Tested by check_default_data_area. */ + +typedef struct data_area_stack_element +{ + struct data_area_stack_element * prev; + v850_data_area data_area; /* current default data area. */ +} data_area_stack_element; + +static data_area_stack_element * data_area_stack = NULL; + +/* Names of the various data areas used on the v850. */ +static tree GHS_default_section_names [(int) COUNT_OF_GHS_SECTION_KINDS]; +static tree GHS_current_section_names [(int) COUNT_OF_GHS_SECTION_KINDS]; + +/* Push a data area onto the stack. */ +static int +push_data_area (data_area) + v850_data_area data_area; +{ + data_area_stack_element * elem; + + elem = (data_area_stack_element *) xmalloc (sizeof (* elem)); + + if (elem == NULL) + return 0; + + elem->prev = data_area_stack; + elem->data_area = data_area; + + data_area_stack = elem; + + return 1; +} + +/* Remove a data area from the stack. */ +static int +pop_data_area (data_area) + v850_data_area data_area; +{ + if (data_area_stack == NULL) + warning ("#pragma GHS endXXXX found without previous startXXX"); + else if (data_area != data_area_stack->data_area) + warning ("#pragma GHS endXXX does not match previous startXXX"); + else + { + data_area_stack_element * elem; + + elem = data_area_stack; + data_area_stack = data_area_stack->prev; + + free (elem); + + return 1; + } + + return 0; +} + +/* Set the machine specific 'interrupt' attribute on the current function. */ +static int +mark_current_function_as_interrupt () +{ + tree name; + + if (current_function_decl == NULL_TREE) + { + warning ("Cannot set interrupt attribute: no current function"); + return 0; + } + + name = get_identifier ("interrupt"); + + if (name == NULL_TREE || TREE_CODE (name) != IDENTIFIER_NODE) + { + warning ("Cannot set interrupt attribute: no such identifier"); + return 0; + } + + return valid_machine_attribute + (name, NULL_TREE, current_function_decl, NULL_TREE); +} + +/* Parse STRING as part of a GHS pragma. + Returns 0 if the pragma has been parsed and there was a problem, + non-zero in all other cases. */ +static int +parse_ghs_pragma_token (string) + char * string; +{ + static enum v850_pragma_state state = V850_PS_START; + static enum v850_pragma_type type = V850_PT_UNKNOWN; + static v850_data_area data_area = DATA_AREA_NORMAL; + static char * data_area_name; + static enum GHS_section_kind GHS_section_kind = GHS_SECTION_KIND_DEFAULT; + + /* If the string is NULL then we have reached the end of the + #pragma construct. Make sure that we are in an end state, and + then implement the pragma's directive. */ + if (string == NULL) + { + int ret_val = 1; + + if (state != V850_PS_SHOULD_BE_DONE + && state != V850_PS_MAYBE_COMMA + && state != V850_PS_MAYBE_SECTION_NAME) + { + if (state != V850_PS_BAD) + warning ("Incomplete #pragma ghs"); + + ret_val = 0; + } + else switch (type) + { + case V850_PT_UNKNOWN: + warning ("Nothing follows #pragma ghs"); + ret_val = 0; + break; + + case V850_PT_INTERRUPT: + ret_val = mark_current_function_as_interrupt (); + break; + + case V850_PT_SECTION: + /* If a section kind has not been specified, then reset + all section names back to their defaults. */ + if (GHS_section_kind == GHS_SECTION_KIND_DEFAULT) + { + int i; + + for (i = COUNT_OF_GHS_SECTION_KINDS; i--;) + GHS_current_section_names [i] = NULL; + } + /* If a section has been specified, then this will be handled + by check_default_section_name (). */ + break; + + case V850_PT_START_SECTION: + ret_val = push_data_area (data_area); + break; + + case V850_PT_END_SECTION: + ret_val = pop_data_area (data_area); + break; + } + + state = V850_PS_START; + type = V850_PT_UNKNOWN; + + return ret_val; + } + + switch (state) + { + case V850_PS_START: + data_area = DATA_AREA_NORMAL; + data_area_name = NULL; + + if (streq (string, "interrupt")) + { + type = V850_PT_INTERRUPT; + state = V850_PS_SHOULD_BE_DONE; + } + else if (streq (string, "section")) + { + type = V850_PT_SECTION; + state = V850_PS_MAYBE_SECTION_NAME; + GHS_section_kind = GHS_SECTION_KIND_DEFAULT; + } + else if (streq (string, "starttda")) + { + type = V850_PT_START_SECTION; + state = V850_PS_SHOULD_BE_DONE; + data_area = DATA_AREA_TDA; + } + else if (streq (string, "endtda")) + { + type = V850_PT_END_SECTION; + state = V850_PS_SHOULD_BE_DONE; + data_area = DATA_AREA_TDA; + } + else if (streq (string, "startsda")) + { + type = V850_PT_START_SECTION; + state = V850_PS_SHOULD_BE_DONE; + data_area = DATA_AREA_SDA; + } + else if (streq (string, "endsda")) + { + type = V850_PT_END_SECTION; + state = V850_PS_SHOULD_BE_DONE; + data_area = DATA_AREA_SDA; + } + else if (streq (string, "startzda")) + { + type = V850_PT_START_SECTION; + state = V850_PS_SHOULD_BE_DONE; + data_area = DATA_AREA_ZDA; + } + else if (streq (string, "endzda")) + { + type = V850_PT_END_SECTION; + state = V850_PS_SHOULD_BE_DONE; + data_area = DATA_AREA_ZDA; + } + else + { + warning ("Unrecognised GHS pragma: '%s'\n", string); + state = V850_PS_BAD; + } + break; + + case V850_PS_SHOULD_BE_DONE: + warning ("Extra text after valid #pragma: '%s'", string); + state = V850_PS_BAD; + break; + + case V850_PS_BAD: + /* Ignore tokens in a pragma that has been diagnosed as being corrupt. */ + break; + + case V850_PS_MAYBE_SECTION_NAME: + state = V850_PS_EXPECTING_EQUALS; + + if (streq (string, "data")) GHS_section_kind = GHS_SECTION_KIND_DATA; + else if (streq (string, "text")) GHS_section_kind = GHS_SECTION_KIND_TEXT; + else if (streq (string, "rodata")) GHS_section_kind = GHS_SECTION_KIND_RODATA; + else if (streq (string, "const")) GHS_section_kind = GHS_SECTION_KIND_RODATA; + else if (streq (string, "rosdata")) GHS_section_kind = GHS_SECTION_KIND_ROSDATA; + else if (streq (string, "rozdata")) GHS_section_kind = GHS_SECTION_KIND_ROZDATA; + else if (streq (string, "sdata")) GHS_section_kind = GHS_SECTION_KIND_SDATA; + else if (streq (string, "tdata")) GHS_section_kind = GHS_SECTION_KIND_TDATA; + else if (streq (string, "zdata")) GHS_section_kind = GHS_SECTION_KIND_ZDATA; + /* According to GHS beta documentation, the following should not be allowed! */ + else if (streq (string, "bss")) GHS_section_kind = GHS_SECTION_KIND_BSS; + else if (streq (string, "zbss")) GHS_section_kind = GHS_SECTION_KIND_ZDATA; + else + { + warning ("Unrecognised section name '%s' in GHS section pragma", + string); + state = V850_PS_BAD; + } + break; + + case V850_PS_EXPECTING_EQUALS: + if (streq (string, "=")) + state = V850_PS_EXPECTING_SECTION_ALIAS; + else + { + warning ("Missing '=' in GHS section pragma"); + state = V850_PS_BAD; + } + break; + + case V850_PS_EXPECTING_SECTION_ALIAS: + if (streq (string, "default")) + GHS_current_section_names [GHS_section_kind] = NULL; + else + GHS_current_section_names [GHS_section_kind] = + build_string (strlen (string) + 1, string); + + state = V850_PS_MAYBE_COMMA; + break; + + case V850_PS_MAYBE_COMMA: + if (streq (string, ",")) + state = V850_PS_MAYBE_SECTION_NAME; + else + { + warning + ("Malformed GHS section pragma: found '%s' instead of a comma", + string); + state = V850_PS_BAD; + } + break; + } + + return 1; +} + +/* Handle the parsing of an entire GHS pragma. */ +int +v850_handle_pragma (p_getc, p_ungetc, name) + int (* p_getc) PROTO ((void)); + void (* p_ungetc) PROTO ((int)); + char * name; +{ + /* Parse characters in the input stream until: + + * end of line + * end of file + * a complete GHS pragma has been parsed + * a corrupted GHS pragma has been parsed + * an unknown pragma is encountered. + + If an unknown pragma is encountered, we must return with + the input stream in the same state as upon entry to this function. + + The first token in the input stream has already been parsed + for us, and is passed as 'name'. */ + + if (! streq (name, "ghs")) + return 0; + + /* We now know that we are parsing a GHS pragma, so we do + not need to preserve the original input stream state. */ + for (;;) + { + static char buffer [128]; + int c; + char * buff; + + /* Skip white space. */ + do + c = p_getc (); + while (c == ' ' || c == '\t'); + + p_ungetc (c); + + if (c == '\n' || c == EOF || c == '\r') + return parse_ghs_pragma_token (NULL); + + /* Read next word. We have to do the parsing ourselves, rather + than calling yylex() because we can be built with front ends + that do not provide such functions. */ + buff = buffer; + * buff ++ = (c = p_getc ()); + + switch (c) + { + case ',': + case '=': + * buff ++ = (c = p_getc ()); + break; + + case '"': + /* Skip opening double parenthesis. */ + -- buff; + + /* Read string. */ + do + * buff ++ = (c = p_getc ()); + while (c != EOF && isascii (c) + && (isalnum (c) || c == '_' || c == '.' || c == ' ') + && (buff < buffer + 126)); + + if (c != '"') + warning ("Missing trailing \" in #pragma ghs"); + else + c = p_getc (); + break; + + default: + while (c != EOF && isascii (c) + && (isalnum (c) || c == '_' || c == '.') + && (buff < buffer + 126)) + * buff ++ = (c = p_getc ()); + break; + } + + p_ungetc (c); + + /* If nothing was read then terminate the parsing. */ + if (buff == buffer + 1) + return parse_ghs_pragma_token (NULL); + + /* Parse and continue. */ + * -- buff = 0; + + parse_ghs_pragma_token (buffer); + } +} + +/* Add data area to the given declaration if a ghs data area pragma is + currently in effect (#pragma ghs startXXX/endXXX). */ +void +v850_set_default_decl_attr (decl) + tree decl; +{ + if (data_area_stack + && data_area_stack->data_area + && current_function_decl == NULL_TREE + && (TREE_CODE (decl) == VAR_DECL || TREE_CODE (decl) == CONST_DECL) + && v850_get_data_area (decl) == DATA_AREA_NORMAL) + v850_set_data_area (decl, data_area_stack->data_area); + + /* Initialise the default names of the v850 specific sections, + if this has not been done before. */ + + if (GHS_default_section_names [(int) GHS_SECTION_KIND_SDATA] == NULL) + { + GHS_default_section_names [(int) GHS_SECTION_KIND_SDATA] + = build_string (sizeof (".sdata")-1, ".sdata"); + + GHS_default_section_names [(int) GHS_SECTION_KIND_ROSDATA] + = build_string (sizeof (".rosdata")-1, ".rosdata"); + + GHS_default_section_names [(int) GHS_SECTION_KIND_TDATA] + = build_string (sizeof (".tdata")-1, ".tdata"); + + GHS_default_section_names [(int) GHS_SECTION_KIND_ZDATA] + = build_string (sizeof (".zdata")-1, ".zdata"); + + GHS_default_section_names [(int) GHS_SECTION_KIND_ROZDATA] + = build_string (sizeof (".rozdata")-1, ".rozdata"); + } + + if (current_function_decl == NULL_TREE + && (TREE_CODE (decl) == VAR_DECL + || TREE_CODE (decl) == CONST_DECL + || TREE_CODE (decl) == FUNCTION_DECL) + && (!DECL_EXTERNAL (decl) || DECL_INITIAL (decl)) + && !DECL_SECTION_NAME (decl)) + { + enum GHS_section_kind kind = GHS_SECTION_KIND_DEFAULT; + tree chosen_section; + + if (TREE_CODE (decl) == FUNCTION_DECL) + kind = GHS_SECTION_KIND_TEXT; + else + { + /* First choose a section kind based on the data area of the decl. */ + switch (v850_get_data_area (decl)) + { + default: + abort (); + + case DATA_AREA_SDA: + kind = ((TREE_READONLY (decl)) + ? GHS_SECTION_KIND_ROSDATA + : GHS_SECTION_KIND_SDATA); + break; + + case DATA_AREA_TDA: + kind = GHS_SECTION_KIND_TDATA; + break; + + case DATA_AREA_ZDA: + kind = ((TREE_READONLY (decl)) + ? GHS_SECTION_KIND_ROZDATA + : GHS_SECTION_KIND_ZDATA); + break; + + case DATA_AREA_NORMAL: /* default data area */ + if (TREE_READONLY (decl)) + kind = GHS_SECTION_KIND_RODATA; + else if (DECL_INITIAL (decl)) + kind = GHS_SECTION_KIND_DATA; + else + kind = GHS_SECTION_KIND_BSS; + } + } + + /* Now, if the section kind has been explicitly renamed, + then attach a section attribute. */ + chosen_section = GHS_current_section_names [(int) kind]; + + /* Otherwise, if this kind of section needs an explicit section + attribute, then also attach one. */ + if (chosen_section == NULL) + chosen_section = GHS_default_section_names [(int) kind]; + + if (chosen_section) + { + /* Only set the section name if specified by a pragma, because + otherwise it will force those variables to get allocated storage + in this module, rather than by the linker. */ + DECL_SECTION_NAME (decl) = chosen_section; + } + } +} + +/* CYGNUS LOCAL v850e */ +/* Return non-zero if the given RTX is suitable for collapsing into a + DISPOSE instruction. */ +int +pattern_is_ok_for_dispose (op, mode) + rtx op; + enum machine_mode mode; +{ + int count = XVECLEN (op, 0); + int i; + + /* If there are no registers to restore then the dispose instruction is not + suitable. */ + if (count <= 2) + return 0; + + /* The pattern matching has already established that we are performing a + function epilogue and that we are popping at least one register. We must + now check the remaining entries in the vector to make sure that they are + also register pops. There is no good reason why there should ever be + anything else in this vector, but being paranoid always helps... + + The test below performs the C equivalent of this machine description + pattern match: + + (set (match_operand:SI n "register_is_ok_for_epilogue" "r") + (mem:SI (plus:SI (reg:SI 3) + (match_operand:SI n "immediate_operand" "i")))) + */ + + for (i = 3; i < count; i++) + { + rtx vector_element = XVECEXP (op, 0, i); + rtx dest; + rtx src; + rtx plus; + + if (GET_CODE (vector_element) != SET) + return 0; + + dest = SET_DEST (vector_element); + src = SET_SRC (vector_element); + + if ( GET_CODE (dest) != REG + || GET_MODE (dest) != SImode + || ! register_is_ok_for_epilogue (dest, SImode) + || GET_CODE (src) != MEM + || GET_MODE (src) != SImode) + return 0; + + plus = XEXP (src, 0); + + if ( GET_CODE (plus) != PLUS + || GET_CODE (XEXP (plus, 0)) != REG + || GET_MODE (XEXP (plus, 0)) != SImode + || REGNO (XEXP (plus, 0)) != STACK_POINTER_REGNUM + || GET_CODE (XEXP (plus, 1)) != CONST_INT) + return 0; + } + + return 1; +} + +/* Construct a DISPOSE instruction that is the equivalent of + the given RTX. We have already verified that this should + be possible. */ +char * +construct_dispose_instruction (op) + rtx op; +{ + int count = XVECLEN (op, 0); + int stack_bytes; + unsigned long int mask; + int i; + static char buff[ 100 ]; /* XXX */ + int use_callt = 0; + + if (count <= 2) + { + error ("Bogus DISPOSE construction: %d\n", count); + return NULL; + } + + /* Work out how many bytes to pop off the stack before retrieving + registers. */ + if (GET_CODE (XVECEXP (op, 0, 1)) != SET) + abort (); + if (GET_CODE (SET_SRC (XVECEXP (op, 0, 1))) != PLUS) + abort (); + if (GET_CODE (XEXP (SET_SRC (XVECEXP (op, 0, 1)), 1)) != CONST_INT) + abort (); + + stack_bytes = INTVAL (XEXP (SET_SRC (XVECEXP (op, 0, 1)), 1)); + + /* Each pop will remove 4 bytes from the stack... */ + + stack_bytes -= (count - 2) * 4; + + /* Make sure that the amount we are popping will fit into the DISPOSE + instruction. */ + if (stack_bytes > 128) + { + error ("Too much stack space to dispose of: %d", stack_bytes); + return NULL; + } + + /* Now compute the bit mask of registers to push. */ + mask = 0; + + for (i = 2; i < count; i++) + { + rtx vector_element = XVECEXP (op, 0, i); + + if (GET_CODE (vector_element) != SET) + abort (); + if (GET_CODE (SET_DEST (vector_element)) != REG) + abort (); + if (! register_is_ok_for_epilogue (SET_DEST (vector_element), SImode)) + abort (); + + if (REGNO (SET_DEST (vector_element)) == 2) + use_callt = 1; + else + mask |= 1 << REGNO (SET_DEST (vector_element)); + } + + if (! TARGET_DISABLE_CALLT + && (use_callt || stack_bytes == 0 || stack_bytes == 16)) + { + if (use_callt) + { + sprintf (buff, "callt ctoff(__callt_return_r2_r%d)", (mask & (1 << 31)) ? 31 : 29); + return buff; + } + else + { + for (i = 20; i < 32; i++) + { + if (mask & (1 << i)) + break; + } + + if (i == 31) + { + sprintf (buff, "callt ctoff(__callt_return_r31c)"); + } + else + { + sprintf (buff, "callt ctoff(__callt_return_r%d_r%d%s)", + i, (mask & (1 << 31)) ? 31 : 29, stack_bytes ? "c" : ""); + } + } + } + else + { + static char regs [100]; /* XXX */ + int done_one; + + /* Generate the DISPOSE instruction. Note we could just issue the + bit mask as a number as the assembler can cope with this, but for + the sake of our readers we turn it into a textual description. */ + + regs[0] = 0; + done_one = 0; + + for (i = 20; i < 32; i++) + { + if (mask & (1 << i)) + { + int first; + + if (done_one) + strcat (regs, ", "); + else + done_one = 1; + + first = i; + strcat (regs, reg_names[ first ]); + + for (i++; i < 32; i++) + if ((mask & (1 << i)) == 0) + break; + + if (i > first + 1) + { + strcat (regs, " - "); + strcat (regs, reg_names[ i - 1 ] ); + } + } + } + + sprintf (buff, "dispose %d {%s}, r31", stack_bytes / 4, regs); + } + + return buff; +} + +/* Return non-zero if the given RTX is suitable for collapsing into a PREPARE + instruction. */ +int +pattern_is_ok_for_prepare (op, mode) + rtx op; + enum machine_mode mode; +{ + int count = XVECLEN (op, 0); + int i; + + /* If there are no registers to restore then the prepare instruction + is not suitable. */ + if (count <= 1) + return 0; + + /* The pattern matching has already established that we are adjusting the + stack and pushing at least one register. We must now check that the + remaining entries in the vector to make sure that they are also register + pushes. + + The test below performs the C equivalent of this machine description + pattern match: + + (set (mem:SI (plus:SI (reg:SI 3) + (match_operand:SI 2 "immediate_operand" "i"))) + (match_operand:SI 3 "register_is_ok_for_epilogue" "r")) + + */ + + for (i = 2; i < count; i++) + { + rtx vector_element = XVECEXP (op, 0, i); + rtx dest; + rtx src; + rtx plus; + + if (GET_CODE (vector_element) != SET) + return 0; + + dest = SET_DEST (vector_element); + src = SET_SRC (vector_element); + + if ( GET_CODE (dest) != MEM + || GET_MODE (dest) != SImode + || GET_CODE (src) != REG + || GET_MODE (src) != SImode + || ! register_is_ok_for_epilogue (src, SImode) + ) + return 0; + + plus = XEXP (dest, 0); + + if ( GET_CODE (plus) != PLUS + || GET_CODE (XEXP (plus, 0)) != REG + || GET_MODE (XEXP (plus, 0)) != SImode + || REGNO (XEXP (plus, 0)) != STACK_POINTER_REGNUM + || GET_CODE (XEXP (plus, 1)) != CONST_INT) + return 0; + + /* If the register is being pushed somewhere other than the stack + space just aquired by the first operand then abandon this quest. + Note: the test is <= becuase both values are negative. */ + if (INTVAL (XEXP (plus, 1)) + <= INTVAL (XEXP (SET_SRC (XVECEXP (op, 0, 0)), 1))) + { + return 0; + } + } + + return 1; +} + +/* Construct a PREPARE instruction that is the equivalent of + the given RTL. We have already verified that this should + be possible. */ +char * +construct_prepare_instruction (op) + rtx op; +{ + int count = XVECLEN (op, 0); + int stack_bytes; + unsigned long int mask; + int i; + static char buff[ 100 ]; /* XXX */ + int use_callt = 0; + + if (count <= 1) + { + error ("Bogus PREPEARE construction: %d\n", count); + return NULL; + } + + /* Work out how many bytes to push onto the stack after storing the + registers. */ + if (GET_CODE (XVECEXP (op, 0, 0)) != SET) + abort (); + if (GET_CODE (SET_SRC (XVECEXP (op, 0, 0))) != PLUS) + abort (); + if (GET_CODE (XEXP (SET_SRC (XVECEXP (op, 0, 0)), 1)) != CONST_INT) + abort (); + + stack_bytes = INTVAL (XEXP (SET_SRC (XVECEXP (op, 0, 0)), 1)); + + /* Each push will put 4 bytes from the stack... */ + stack_bytes += (count - 1) * 4; + + /* Make sure that the amount we are popping will fit into the DISPOSE + instruction. */ + if (stack_bytes < -128) + { + error ("Too much stack space to prepare: %d", stack_bytes); + return NULL; + } + + /* Now compute the bit mask of registers to push. */ + mask = 0; + for (i = 1; i < count; i++) + { + rtx vector_element = XVECEXP (op, 0, i); + + if (GET_CODE (vector_element) != SET) + abort (); + if (GET_CODE (SET_SRC (vector_element)) != REG) + abort (); + if (! register_is_ok_for_epilogue (SET_SRC (vector_element), SImode)) + abort (); + + if (REGNO (SET_SRC (vector_element)) == 2) + use_callt = 1; + else + mask |= 1 << REGNO (SET_SRC (vector_element)); + } + + if ((! TARGET_DISABLE_CALLT) + && (use_callt || stack_bytes == 0 || stack_bytes == -16)) + { + if (use_callt) + { + sprintf (buff, "callt ctoff(__callt_save_r2_r%d)", (mask & (1 << 31)) ? 31 : 29 ); + return buff; + } + + for (i = 20; i < 32; i++) + { + if (mask & (1 << i)) + break; + } + + if (i == 31) + { + sprintf (buff, "callt ctoff(__callt_save_r31c)"); + } + else + { + sprintf (buff, "callt ctoff(__callt_save_r%d_r%d%s)", + i, (mask & (1 << 31)) ? 31 : 29, stack_bytes ? "c" : ""); + } + } + else + { + static char regs [100]; /* XXX */ + int done_one; + + + /* Generate the PREPARE instruction. Note we could just issue the + bit mask as a number as the assembler can cope with this, but for + the sake of our readers we turn it into a textual description. */ + + regs[0] = 0; + done_one = 0; + + for (i = 20; i < 32; i++) + { + if (mask & (1 << i)) + { + int first; + + if (done_one) + strcat (regs, ", "); + else + done_one = 1; + + first = i; + strcat (regs, reg_names[ first ]); + + for (i++; i < 32; i++) + if ((mask & (1 << i)) == 0) + break; + + if (i > first + 1) + { + strcat (regs, " - "); + strcat (regs, reg_names[ i - 1 ] ); + } + } + } + + sprintf (buff, "prepare {%s}, %d", regs, (- stack_bytes) / 4); + } + + return buff; +} + +/* END CYGNUS LOCAL */ |