--- /dev/null
+/* Output routines for GCC for ARM/RISCiX.
+ Copyright (C) 1991 Free Software Foundation, Inc.
+ Contributed by Pieter `Tiggr' Schoenmakers (rcpieter@win.tue.nl)
+ and Martin Simmons (@harleqn.co.uk).
+
+This file is part of GNU CC.
+
+GNU CC is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU CC is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU CC; see the file COPYING. If not, write to
+the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#include <stdio.h>
+#include <assert.h>
+#include "config.h"
+#include "rtl.h"
+#include "regs.h"
+#include "hard-reg-set.h"
+#include "real.h"
+#include "insn-config.h"
+#include "conditions.h"
+#include "insn-flags.h"
+#include "output.h"
+#include "insn-attr.h"
+#include "flags.h"
+
+/* The maximum number of insns skipped which will be conditionalised if
+ possible. */
+#define MAX_INSNS_SKIPPED 5
+
+/* Some function declarations. */
+extern void *xmalloc ();
+extern FILE *asm_out_file;
+extern char *output_multi_immediate ();
+extern char *arm_output_asm_insn ();
+extern void arm_increase_location ();
+
+/* In case of a PRE_INC, POST_INC, PRE_DEC, POST_DEC memory reference, we
+ must report the mode of the memory reference from PRINT_OPERAND to
+ PRINT_OPERAND_ADDRESS. */
+int output_memory_reference_mode;
+
+/* Nonzero if the prologue must setup `fp'. */
+int current_function_anonymous_args;
+
+/* Location counter of .text segment. */
+int arm_text_location = 0;
+
+/* A hash table is used to store text segment labels and their associated
+ offset from the start of the text segment. */
+struct label_offset
+{
+ char *name;
+ int offset;
+ struct label_offset *cdr;
+};
+
+#define LABEL_HASH_SIZE 257
+
+static struct label_offset *offset_table[LABEL_HASH_SIZE];
+
+/* For an explanation of these variables, see final_prescan_insn below. */
+int arm_ccfsm_state;
+int arm_current_cc;
+rtx arm_target_insn;
+int arm_target_label;
+char *arm_condition_codes[];
+\f
+/* Return the number of mov instructions needed to get the constant VALUE into
+ a register. */
+
+int
+arm_const_nmoves (value)
+ register int value;
+{
+ register int i;
+
+ if (value == 0)
+ return (1);
+ for (i = 0; value; i++, value &= ~0xff)
+ while ((value & 3) == 0)
+ value = (value >> 2) | ((value & 3) << 30);
+ return (i);
+} /* arm_const_nmoves */
+
+
+/* Return TRUE if int I is a valid immediate ARM constant. */
+
+int
+const_ok_for_arm (i)
+ int i;
+{
+ unsigned int mask = ~0xFF;
+
+ do
+ {
+ if ((i & mask) == 0)
+ return(TRUE);
+ mask = (mask << 2) | (mask >> (32 - 2));
+ } while (mask != ~0xFF);
+
+ return (FALSE);
+} /* const_ok_for_arm */
+
+/* Return TRUE if rtx X is a valid immediate FPU constant. */
+
+int
+const_double_rtx_ok_for_fpu (x)
+ rtx x;
+{
+ double d;
+ union real_extract u;
+ u.i[0] = CONST_DOUBLE_LOW(x);
+ u.i[1] = CONST_DOUBLE_HIGH(x);
+ d = u.d;
+
+ return (d == 0.0 || d == 1.0 || d == 2.0 || d == 3.0
+ || d == 4.0 || d == 5.0 || d == 0.5 || d == 10.0);
+} /* const_double_rtx_ok_for_fpu */
+\f
+/* Predicates for `match_operand' and `match_operator'. */
+
+/* Return TRUE for valid operands for the rhs of an ARM instruction. */
+
+int
+arm_rhs_operand (op, mode)
+ rtx op;
+ enum machine_mode mode;
+{
+ return (register_operand (op, mode)
+ || (GET_CODE (op) == CONST_INT && const_ok_for_arm (INTVAL (op))));
+} /* arm_rhs_operand */
+
+/* Return TRUE for valid operands for the rhs of an FPU instruction. */
+
+int
+fpu_rhs_operand (op, mode)
+ rtx op;
+ enum machine_mode mode;
+{
+ if (register_operand (op, mode))
+ return(TRUE);
+ else if (GET_CODE (op) == CONST_DOUBLE)
+ return (const_double_rtx_ok_for_fpu (op));
+ else return (FALSE);
+} /* fpu_rhs_operand */
+
+/* Return nonzero if OP is a constant power of two. */
+
+int
+power_of_two_operand (op, mode)
+ rtx op;
+ enum machine_mode mode;
+{
+ if (GET_CODE (op) == CONST_INT)
+ {
+ int value = INTVAL(op);
+ return (value != 0 && (value & (value-1)) == 0);
+ }
+ return (FALSE);
+} /* power_of_two_operand */
+
+/* Return TRUE for a valid operand of a DImode operation.
+ Either: REG, CONST_DOUBLE or MEM(offsettable).
+ Note that this disallows MEM(REG+REG). */
+
+int
+di_operand (op, mode)
+ rtx op;
+ enum machine_mode mode;
+{
+ if (register_operand (op, mode))
+ return (TRUE);
+
+ switch (GET_CODE (op))
+ {
+ case CONST_DOUBLE:
+ case CONST_INT:
+ return (TRUE);
+ case MEM:
+ return (memory_address_p (DImode, XEXP (op, 0))
+ && offsettable_address_p (FALSE, DImode, XEXP (op, 0)));
+ default:
+ return (FALSE);
+ }
+} /* di_operand */
+
+/* Return TRUE for valid index operands. */
+
+int
+index_operand (op, mode)
+ rtx op;
+ enum machine_mode mode;
+{
+ return (register_operand(op, mode)
+ || (immediate_operand (op, mode) && abs (INTVAL (op)) < 4096));
+} /* index_operand */
+
+/* Return TRUE for arithmetic operators which can be combined with a multiply
+ (shift). */
+
+int
+shiftable_operator (x, mode)
+ rtx x;
+ enum machine_mode mode;
+{
+ if (GET_MODE (x) != mode)
+ return FALSE;
+ else
+ {
+ enum rtx_code code = GET_CODE (x);
+
+ return (code == PLUS || code == MINUS
+ || code == IOR || code == XOR || code == AND);
+ }
+} /* shiftable_operator */
+
+/* Return TRUE for shift operators. */
+
+int
+shift_operator (x, mode)
+ rtx x;
+ enum machine_mode mode;
+{
+ if (GET_MODE (x) != mode)
+ return FALSE;
+ else
+ {
+ enum rtx_code code = GET_CODE (x);
+
+ return (code == ASHIFT || code == LSHIFT
+ || code == ASHIFTRT || code == LSHIFTRT);
+ }
+} /* shift_operator */
+\f
+/* Routines to output assembly language. */
+
+/* Output the operands of a LDM/STM instruction to STREAM.
+ MASK is the ARM register set mask of which only bits 0-15 are important.
+ INSTR is the possibly suffixed base register. HAT unequals zero if a hat
+ must follow the register list. */
+
+void
+print_multi_reg (stream, instr, mask, hat)
+ FILE *stream;
+ char *instr;
+ int mask, hat;
+{
+ int i;
+ int not_first = FALSE;
+
+ fprintf (stream, "\t%s, {", instr);
+ for (i = 0; i < 16; i++)
+ if (mask & (1 << i))
+ {
+ if (not_first)
+ fprintf (stream, ", ");
+ fprintf (stream, "%s", reg_names[i]);
+ not_first = TRUE;
+ }
+ fprintf (stream, "}%s\n", hat ? "^" : "");
+} /* print_multi_reg */
+
+/* Output a 'call' insn. */
+
+char *
+output_call (operands)
+ rtx operands[];
+{
+ operands[0] = XEXP (operands[0], 0);
+
+ /* Handle calls to lr using ip (which may be clobbered in subr anyway). */
+
+ if (REGNO (operands[0]) == 14)
+ {
+ operands[0] = gen_rtx (REG, SImode, 12);
+ arm_output_asm_insn ("mov\t%0, lr", operands);
+ }
+ arm_output_asm_insn ("mov\tlr, pc", operands);
+ arm_output_asm_insn ("mov\tpc, %0", operands);
+ return ("");
+} /* output_call */
+
+/* Output a move from arm registers to an fpu registers.
+ OPERANDS[0] is an fpu register.
+ OPERANDS[1] is the first registers of an arm register pair. */
+
+char *
+output_mov_double_fpu_from_arm (operands)
+ rtx operands[];
+{
+ int arm_reg0 = REGNO (operands[1]);
+ rtx ops[2];
+
+ if (arm_reg0 == 12)
+ abort();
+ ops[0] = gen_rtx (REG, SImode, arm_reg0);
+ ops[1] = gen_rtx (REG, SImode, 1 + arm_reg0);
+ arm_output_asm_insn ("stmfd\tsp!, {%0, %1}", ops);
+ arm_output_asm_insn ("ldfd\t%0, [sp], #8", operands);
+ return ("");
+} /* output_mov_double_fpu_from_arm */
+
+/* Output a move from an fpu register to arm registers.
+ OPERANDS[0] is the first registers of an arm register pair.
+ OPERANDS[1] is an fpu register. */
+
+char *
+output_mov_double_arm_from_fpu (operands)
+ rtx operands[];
+{
+ int arm_reg0 = REGNO (operands[0]);
+ rtx ops[2];
+
+ if (arm_reg0 == 12)
+ abort();
+ ops[0] = gen_rtx (REG, SImode, arm_reg0);
+ ops[1] = gen_rtx (REG, SImode, 1 + arm_reg0);
+ arm_output_asm_insn ("stfd\t%1, [sp, #-8]!", operands);
+ arm_output_asm_insn ("ldmfd\tsp!, {%0, %1}", ops);
+ return("");
+} /* output_mov_double_arm_from_fpu */
+
+/* Output a move between double words.
+ It must be REG<-REG, REG<-CONST_DOUBLE, REG<-CONST_INT, REG<-MEM
+ or MEM<-REG and all MEMs must be offsettable addresses. */
+
+char *
+output_move_double (operands)
+ rtx operands[];
+{
+ enum rtx_code code0 = GET_CODE (operands[0]);
+ enum rtx_code code1 = GET_CODE (operands[1]);
+ rtx otherops[2];
+
+ if (code0 == REG)
+ {
+ int reg0 = REGNO (operands[0]);
+
+ otherops[0] = gen_rtx (REG, SImode, 1 + reg0);
+ if (code1 == REG)
+ {
+ int reg1 = REGNO (operands[1]);
+ if (reg1 == 12)
+ abort();
+ otherops[1] = gen_rtx (REG, SImode, 1 + reg1);
+
+ /* Ensure the second source is not overwritten */
+ if (reg0 == 1 + reg1)
+ {
+ arm_output_asm_insn("mov\t%0, %1", otherops);
+ arm_output_asm_insn("mov\t%0, %1", operands);
+ }
+ else
+ {
+ arm_output_asm_insn("mov\t%0, %1", operands);
+ arm_output_asm_insn("mov\t%0, %1", otherops);
+ }
+ }
+ else if (code1 == CONST_DOUBLE)
+ {
+ otherops[1] = gen_rtx (CONST_INT, VOIDmode,
+ CONST_DOUBLE_HIGH (operands[1]));
+ operands[1] = gen_rtx (CONST_INT, VOIDmode,
+ CONST_DOUBLE_LOW (operands[1]));
+ arm_output_asm_insn ("mov\t%0, %1", operands);
+ arm_output_asm_insn ("mov\t%0, %1", otherops);
+ }
+ else if (code1 == CONST_INT)
+ {
+ otherops[1] = const0_rtx;
+ arm_output_asm_insn ("mov\t%0, %1", operands);
+ arm_output_asm_insn ("mov\t%0, %1", otherops);
+ }
+ else if (code1 == MEM)
+ {
+ if (GET_CODE (XEXP (operands[1], 0)) == REG)
+ {
+ /* Handle the simple case where address is [r, #0] more
+ efficient. */
+ operands[1] = XEXP (operands[1], 0);
+ arm_output_asm_insn ("ldmia\t%1, %M0", operands);
+ }
+ else
+ {
+ otherops[1] = adj_offsettable_operand (operands[1], 4);
+ /* Take care of overlapping base/data reg. */
+ if (reg_mentioned_p (operands[0], operands[1]))
+ {
+ arm_output_asm_insn ("ldr\t%0, %1", otherops);
+ arm_output_asm_insn ("ldr\t%0, %1", operands);
+ }
+ else
+ {
+ arm_output_asm_insn ("ldr\t%0, %1", operands);
+ arm_output_asm_insn ("ldr\t%0, %1", otherops);
+ }
+ }
+ }
+ else abort(); /* Constraints should prevent this */
+ }
+ else if (code0 == MEM && code1 == REG)
+ {
+ if (REGNO (operands[1]) == 12)
+ abort();
+
+ if (GET_CODE (XEXP (operands[0], 0)) == REG)
+ {
+ operands[0] = XEXP (operands[0], 0);
+ arm_output_asm_insn ("stmia\t%0, %M1", operands);
+ }
+ else
+ {
+ otherops[0] = adj_offsettable_operand (operands[0], 4);
+ otherops[1] = gen_rtx (REG, SImode, 1 + REGNO (operands[1]));
+ arm_output_asm_insn ("str\t%1, %0", operands);
+ arm_output_asm_insn ("str\t%1, %0", otherops);
+ }
+ }
+ else abort(); /* Constraints should prevent this */
+
+ return("");
+} /* output_move_double */
+
+
+/* Output an arbitrary MOV reg, #n.
+ OPERANDS[0] is a register. OPERANDS[1] is a const_int. */
+
+char *
+output_mov_immediate (operands)
+ rtx operands[2];
+{
+ int n = INTVAL (operands[1]);
+ int n_ones = 0;
+ int i;
+
+ /* Try to use one MOV */
+
+ if (const_ok_for_arm (n))
+ return (arm_output_asm_insn ("mov\t%0, %1", operands));
+
+ /* Try to use one MVN */
+
+ if (const_ok_for_arm(~n))
+ {
+ operands[1] = gen_rtx (CONST_INT, VOIDmode, ~n);
+ return (arm_output_asm_insn ("mvn\t%0, %1", operands));
+ }
+
+ /* If all else fails, make it out of ORRs or BICs as appropriate. */
+
+ for (i=0; i < 32; i++)
+ if (n & 1 << i)
+ n_ones++;
+
+ if (n_ones > 16) /* Shorter to use MVN with BIC in this case. */
+ output_multi_immediate(operands, "mvn\t%0, %1", "bic\t%0, %0, %1", 1, ~n);
+ else
+ output_multi_immediate(operands, "mov\t%0, %1", "orr\t%0, %0, %1", 1, n);
+ return("");
+} /* output_mov_immediate */
+
+
+/* Output an ADD r, s, #n where n may be too big for one instruction. If
+ adding zero to one register, output nothing. */
+
+char *
+output_add_immediate (operands)
+ rtx operands[3];
+{
+ int n = INTVAL (operands[2]);
+
+ if (n != 0 || REGNO (operands[0]) != REGNO (operands[1]))
+ {
+ if (n < 0)
+ output_multi_immediate (operands,
+ "sub\t%0, %1, %2", "sub\t%0, %0, %2", 2, -n);
+ else
+ output_multi_immediate (operands,
+ "add\t%0, %1, %2", "add\t%0, %0, %2", 2, n);
+ }
+ return("");
+} /* output_add_immediate */
+
+
+/* Output a multiple immediate operation.
+ OPERANDS is the vector of operands referred to in the output patterns.
+ INSTR1 is the output pattern to use for the first constant.
+ INSTR2 is the output pattern to use for subsequent constants.
+ IMMED_OP is the index of the constant slot in OPERANDS.
+ N is the constant value. */
+
+char *
+output_multi_immediate (operands, instr1, instr2, immed_op, n)
+ rtx operands[];
+ char *instr1, *instr2;
+ int immed_op, n;
+{
+ if (n == 0)
+ {
+ operands[immed_op] = const0_rtx;
+ arm_output_asm_insn (instr1, operands); /* Quick and easy output */
+ }
+ else
+ {
+ int i;
+ char *instr = instr1;
+
+ /* Note that n is never zero here (which would give no output) */
+
+ for (i = 0; i < 32; i += 2)
+ {
+ if (n & (3 << i))
+ {
+ operands[immed_op] = gen_rtx (CONST_INT, VOIDmode,
+ n & (255 << i));
+ arm_output_asm_insn (instr, operands);
+ instr = instr2;
+ i += 6;
+ }
+ }
+ }
+ return ("");
+} /* output_multi_immediate */
+
+
+/* Return the appropriate ARM instruction for the operation code.
+ The returned result should not be overwritten. OP is the rtx of the
+ operation. SHIFT_FIRST_ARG is TRUE if the first argument of the operator
+ was shifted. */
+
+char *
+arithmetic_instr (op, shift_first_arg)
+ rtx op;
+{
+ switch (GET_CODE(op))
+ {
+ case PLUS:
+ return ("add");
+ case MINUS:
+ if (shift_first_arg)
+ return ("rsb");
+ else
+ return ("sub");
+ case IOR:
+ return ("orr");
+ case XOR:
+ return ("eor");
+ case AND:
+ return ("and");
+ default:
+ abort();
+ }
+ return (""); /* stupid cc */
+} /* arithmetic_instr */
+
+
+/* Ensure valid constant shifts and return the appropriate shift mnemonic
+ for the operation code. The returned result should not be overwritten.
+ OP is the rtx code of the shift.
+ SHIFT_PTR points to the shift size operand. */
+
+char *
+shift_instr (op, shift_ptr)
+ enum rtx_code op;
+ rtx *shift_ptr;
+{
+ int min_shift = 0;
+ int max_shift = 31;
+ char *mnem;
+
+ switch (op)
+ {
+ case ASHIFT:
+ mnem = "asl";
+ break;
+ case LSHIFT:
+ mnem = "lsl";
+ break;
+ case ASHIFTRT:
+ mnem = "asr";
+ max_shift = 32;
+ break;
+ case LSHIFTRT:
+ mnem = "lsr";
+ max_shift = 32;
+ break;
+ default:
+ abort();
+ }
+
+ if (GET_CODE (*shift_ptr) == CONST_INT)
+ {
+ int shift = INTVAL (*shift_ptr);
+
+ if (shift < min_shift)
+ *shift_ptr = gen_rtx (CONST_INT, VOIDmode, 0);
+ else if (shift > max_shift)
+ *shift_ptr = gen_rtx (CONST_INT, VOIDmode, max_shift);
+ }
+ return (mnem);
+} /* shift_instr */
+
+
+/* Obtain the shift from the POWER of two. */
+
+int
+int_log2 (power)
+ unsigned int power;
+{
+ int shift = 0;
+
+ while (((1 << shift) & power) == 0)
+ {
+ if (shift > 31)
+ abort();
+ shift++;
+ }
+ return (shift);
+} /* int_log2 */
+
+
+/* Output an arithmetic instruction which may set the condition code.
+ OPERANDS[0] is the destination register.
+ OPERANDS[1] is the arithmetic operator expression.
+ OPERANDS[2] is the left hand argument.
+ OPERANDS[3] is the right hand argument.
+ CONST_FIRST_ARG is TRUE if the first argument of the operator was constant.
+ SET_COND is TRUE when the condition code should be set. */
+
+char *
+output_arithmetic (operands, const_first_arg, set_cond)
+ rtx operands[4];
+ int const_first_arg;
+ int set_cond;
+{
+ char mnemonic[80];
+ char *instr = arithmetic_instr (operands[1], const_first_arg);
+
+ sprintf (mnemonic, "%s%s\t%%0, %%2, %%3", instr, set_cond ? "s" : "");
+ return (arm_output_asm_insn (mnemonic, operands));
+} /* output_arithmetic */
+
+
+/* Output an arithmetic instruction with a shift.
+ OPERANDS[0] is the destination register.
+ OPERANDS[1] is the arithmetic operator expression.
+ OPERANDS[2] is the unshifted register.
+ OPERANDS[3] is the shift operator expression.
+ OPERANDS[4] is the shifted register.
+ OPERANDS[5] is the shift constant or register.
+ SHIFT_FIRST_ARG is TRUE if the first argument of the operator was shifted.
+ SET_COND is TRUE when the condition code should be set. */
+
+char *
+output_arithmetic_with_shift (operands, shift_first_arg, set_cond)
+ rtx operands[6];
+ int shift_first_arg;
+ int set_cond;
+{
+ char mnemonic[80];
+ char *instr = arithmetic_instr (operands[1], shift_first_arg);
+ char *condbit = set_cond ? "s" : "";
+ char *shift = shift_instr (GET_CODE (operands[3]), &operands[5]);
+
+ sprintf (mnemonic, "%s%s\t%%0, %%2, %%4, %s %%5", instr, condbit, shift);
+ return (arm_output_asm_insn (mnemonic, operands));
+} /* output_arithmetic_with_shift */
+
+
+/* Output an arithmetic instruction with a power of two multiplication.
+ OPERANDS[0] is the destination register.
+ OPERANDS[1] is the arithmetic operator expression.
+ OPERANDS[2] is the unmultiplied register.
+ OPERANDS[3] is the multiplied register.
+ OPERANDS[4] is the constant multiple (power of two).
+ SHIFT_FIRST_ARG is TRUE if the first arg of the operator was multiplied. */
+
+char *
+output_arithmetic_with_immediate_multiply (operands, shift_first_arg)
+ rtx operands[5];
+ int shift_first_arg;
+{
+ char mnemonic[80];
+ char *instr = arithmetic_instr (operands[1], shift_first_arg);
+ int shift = int_log2 (INTVAL (operands[4]));
+
+ sprintf (mnemonic, "%s\t%%0, %%2, %%3, asl#%d", instr, shift);
+ return (arm_output_asm_insn (mnemonic, operands));
+} /* output_arithmetic_with_immediate_multiply */
+
+
+/* Output a move with a shift.
+ OP is the shift rtx code.
+ OPERANDS[0] = destination register.
+ OPERANDS[1] = source register.
+ OPERANDS[2] = shift constant or register. */
+
+char *
+output_shifted_move (op, operands)
+ enum rtx_code op;
+ rtx operands[2];
+{
+ char mnemonic[80];
+
+ if (GET_CODE (operands[2]) == CONST_INT && INTVAL (operands[2]) == 0)
+ sprintf (mnemonic, "mov\t%%0, %%1");
+ else
+ sprintf (mnemonic, "mov\t%%0, %%1, %s %%2",
+ shift_instr (op, &operands[2]));
+ return (arm_output_asm_insn (mnemonic, operands));
+} /* output_shifted_move */
+
+
+/* Output a .ascii pseudo-op, keeping track of lengths. This is because
+ /bin/as is horribly restrictive. */
+
+void
+output_ascii_pseudo_op (stream, p, len)
+ FILE *stream;
+ char *p;
+ int len;
+{
+ int i;
+ int len_so_far = 1000;
+ int chars_so_far = 0;
+
+ for (i = 0; i < len; i++)
+ {
+ register int c = p[i];
+
+ if (len_so_far > 50)
+ {
+ if (chars_so_far)
+ fputs ("\"\n", stream);
+ fputs ("\t.ascii\t\"", stream);
+ len_so_far = 0;
+ arm_increase_location (chars_so_far);
+ chars_so_far = 0;
+ }
+
+ if (c == '\"' || c == '\\')
+ {
+ putc('\\', stream);
+ len_so_far++;
+ }
+ if (c >= ' ' && c < 0177)
+ {
+ putc (c, stream);
+ len_so_far++;
+ }
+ else
+ {
+ fprintf (stream, "\\%03o", c);
+ len_so_far +=4;
+ }
+ chars_so_far++;
+ }
+ fputs ("\"\n", stream);
+ arm_increase_location (chars_so_far);
+} /* output_ascii_pseudo_op */
+\f
+void
+output_prologue (f, frame_size)
+ FILE *f;
+ int frame_size;
+{
+
+ int reg, live_regs_mask = 0, code_size = 0;
+ rtx operands[3];
+
+ /* Nonzero if the `fp' (argument pointer) register is needed. */
+ int fp_needed = 0;
+
+ /* Nonzero if we must stuff some register arguments onto the stack as if
+ they were passed there. */
+ int store_arg_regs = 0;
+
+ fprintf (f, "\t@ args = %d, pretend = %d, frame = %d\n",
+ current_function_args_size, current_function_pretend_args_size, frame_size);
+ fprintf (f, "\t@ frame_pointer_needed = %d, current_function_anonymous_args = %d\n",
+ frame_pointer_needed, current_function_anonymous_args);
+
+ if (current_function_pretend_args_size || current_function_args_size
+ || frame_pointer_needed || current_function_anonymous_args || TARGET_APCS)
+ fp_needed = 1;
+
+ if (current_function_anonymous_args && current_function_pretend_args_size)
+ store_arg_regs = 1;
+
+ for (reg = 4; reg < 10; reg++)
+ if (regs_ever_live[reg])
+ live_regs_mask |= (1 << reg);
+
+ if (fp_needed)
+ {
+ live_regs_mask |= 0xD800;
+ /* The following statement is probably redundant now
+ because the frame pointer is recorded in regs_ever_live. */
+ if (frame_pointer_needed)
+ live_regs_mask |= (1 << FRAME_POINTER_REGNUM);
+ fputs ("\tmov\tip, sp\n", f);
+ code_size += 4;
+ }
+ else if (regs_ever_live[14])
+ live_regs_mask |= 0x4000;
+
+ /* If CURRENT_FUNCTION_PRETEND_ARGS_SIZE, adjust the stack pointer to make
+ room. If also STORE_ARG_REGS store the argument registers involved in
+ the created slot (this is for stdarg and varargs). */
+ if (current_function_pretend_args_size)
+ {
+ if (store_arg_regs)
+ {
+ int arg_size, mask = 0;
+
+ assert (current_function_pretend_args_size <= 16);
+ for (reg = 3, arg_size = current_function_pretend_args_size;
+ arg_size > 0; reg--, arg_size -= 4)
+ mask |= (1 << reg);
+ print_multi_reg (f, "stmfd\tsp!", mask, FALSE);
+ }
+ else
+ {
+ operands[0] = operands[1] = stack_pointer_rtx;
+ operands[2] = gen_rtx (CONST_INT, VOIDmode,
+ -current_function_pretend_args_size);
+ output_add_immediate (operands);
+ }
+ }
+
+ if (live_regs_mask)
+ {
+ print_multi_reg (f, "stmfd\tsp!", live_regs_mask, FALSE);
+ code_size += 4;
+ }
+
+ for (reg = 23; reg > 19; reg--)
+ if (regs_ever_live[reg])
+ {
+ fprintf (f, "\tstfe\t%s, [sp, #-12]!\n", reg_names[reg]);
+ code_size += 4;
+ }
+
+ if (fp_needed)
+ {
+ /* Make `fp' point to saved value of `pc'. */
+
+ operands[0] = arg_pointer_rtx;
+ operands[1] = gen_rtx (REG, SImode, 12);
+ operands[2] = gen_rtx (CONST_INT, VOIDmode,
+ - (4 + current_function_pretend_args_size));
+ output_add_immediate (operands);
+ }
+
+ if (frame_pointer_needed)
+ {
+ fprintf (f, "\tmov\trfp, sp\n");
+ code_size += 4;
+ }
+
+ if (frame_size)
+ {
+ operands[0] = operands[1] = stack_pointer_rtx;
+ operands[2] = gen_rtx (CONST_INT, VOIDmode, -frame_size);
+ output_add_immediate (operands);
+ }
+
+ arm_increase_location (code_size);
+} /* output_prologue */
+
+
+void
+output_epilogue (f, frame_size)
+ FILE *f;
+ int frame_size;
+{
+ int reg, live_regs_mask = 0, code_size = 0, fp_needed = 0;
+ rtx operands[3];
+
+ if (current_function_pretend_args_size || current_function_args_size
+ || frame_pointer_needed || current_function_anonymous_args || TARGET_APCS)
+ fp_needed = 1;
+
+ for (reg = 4; reg < 10; reg++)
+ if (regs_ever_live[reg])
+ live_regs_mask |= (1 << reg);
+
+ if (fp_needed)
+ {
+ live_regs_mask |= 0xA800;
+ if (frame_pointer_needed)
+ live_regs_mask |= (1 << FRAME_POINTER_REGNUM);
+ }
+ else if (regs_ever_live[14])
+ live_regs_mask |= 0x4000;
+
+ for (reg = 20; reg < 24; reg++)
+ if (regs_ever_live[reg])
+ {
+ fprintf (f, "\tldfe\t%s, [%s], #12\n", reg_names[reg],
+ frame_pointer_needed ? "rfp" : "sp");
+ code_size += 4;
+ }
+
+ if (fp_needed)
+ {
+ print_multi_reg (f, "ldmea\tfp", live_regs_mask, TRUE);
+ code_size += 4;
+ }
+ else
+ {
+ if (current_function_pretend_args_size == 0 && regs_ever_live[14])
+ {
+ print_multi_reg (f, "ldmfd\tsp!",
+ (live_regs_mask & ~0x4000) | 0x8000, TRUE);
+ code_size += 4;
+ }
+ else
+ {
+ if (live_regs_mask)
+ {
+ print_multi_reg (f, "ldmfd\tsp!", live_regs_mask, FALSE);
+ code_size += 4;
+ }
+ if (current_function_pretend_args_size)
+ {
+ operands[0] = operands[1] = stack_pointer_rtx;
+ operands[2] = gen_rtx (CONST_INT, VOIDmode,
+ current_function_pretend_args_size);
+ output_add_immediate (operands);
+ }
+ fputs ("\tmovs\tpc, lr\n", f);
+ code_size += 4;
+ }
+ }
+ arm_increase_location (code_size);
+ current_function_anonymous_args = 0;
+} /* output_epilogue */
+\f
+/* Increase the `arm_text_location' by AMOUNT if we're in the text
+ segment. */
+
+void
+arm_increase_location (amount)
+ int amount;
+{
+ if (in_text_section ())
+ arm_text_location += amount;
+} /* arm_increase_location */
+
+
+/* Like output_asm_insn (), but also increases the arm_text_location (if in
+ the .text segment, of course, even though this will always be true).
+ Returns the empty string. */
+
+char *
+arm_output_asm_insn (template, operands)
+ char *template;
+ rtx *operands;
+{
+ extern FILE *asm_out_file;
+
+ output_asm_insn (template, operands);
+ if (in_text_section ())
+ arm_text_location += 4;
+ fflush (asm_out_file);
+ return ("");
+} /* arm_output_asm_insn */
+
+
+/* Output a label definition. If this label is within the .text segment, it
+ is stored in OFFSET_TABLE, to be used when building `llc' instructions.
+ Maybe GCC remembers names not starting with a `*' for a long time, but this
+ is a minority anyway, so we just make a copy. Do not store the leading `*'
+ if the name starts with one. */
+
+void
+arm_asm_output_label (stream, name)
+ FILE *stream;
+ char *name;
+{
+ char *real_name, *s;
+ struct label_offset *cur;
+ int hash = 0;
+
+ assemble_name (stream, name);
+ fputs (":\n", stream);
+ if (! in_text_section ())
+ return;
+
+ if (name[0] == '*')
+ {
+ real_name = xmalloc (1 + strlen (&name[1]));
+ strcpy (real_name, &name[1]);
+ }
+ else
+ {
+ real_name = xmalloc (2 + strlen (name));
+ strcpy (real_name, "_");
+ strcat (real_name, name);
+ }
+ for (s = real_name; *s; s++)
+ hash += *s;
+ hash = hash % LABEL_HASH_SIZE;
+ cur = xmalloc (sizeof (struct label_offset));
+ cur->name = real_name;
+ cur->offset = arm_text_location;
+ cur->cdr = offset_table[hash];
+ offset_table[hash] = cur;
+} /* arm_asm_output_label */
+
+
+/* Output the instructions needed to perform what Martin's /bin/as called
+ llc: load an SImode thing from the function's constant pool.
+
+ XXX This could be enhanced in that we do not really need a pointer in the
+ constant pool pointing to the real thing. If we can address this pointer,
+ we can also address what it is pointing at, in fact, anything in the text
+ segment which has been defined already within this .s file. */
+
+char *
+arm_output_llc (operands)
+ rtx *operands;
+{
+ char *s, *name = XSTR (XEXP (operands[1], 0), 0);
+ struct label_offset *he;
+ int hash = 0, conditional = (arm_ccfsm_state == 3 || arm_ccfsm_state == 4);
+
+ if (*name != '*')
+ abort ();
+
+ for (s = &name[1]; *s; s++)
+ hash += *s;
+ hash = hash % LABEL_HASH_SIZE;
+ he = offset_table[hash];
+ while (he && strcmp (he->name, &name[1]))
+ he = he->cdr;
+
+ if (!he)
+ abort ();
+
+ if (arm_text_location + 8 - he->offset < 4095)
+ {
+ fprintf (asm_out_file, "\tldr%s\t%s, [pc, #%s - . - 8]\n",
+ conditional ? arm_condition_codes[arm_current_cc] : "",
+ reg_names[REGNO (operands[0])], &name[1]);
+ arm_increase_location (4);
+ return ("");
+ }
+ else
+ {
+ int offset = - (arm_text_location + 8 - he->offset);
+ char *reg_name = reg_names[REGNO (operands[0])];
+
+ /* ??? This is a hack, assuming the constant pool never is more than
+ (1 + 255) * 4096 == 1Meg away from the PC. */
+
+ if (offset > 1000000)
+ abort ();
+
+ fprintf (asm_out_file, "\tsub%s\t%s, pc, #(8 + . - %s) & ~4095\n",
+ conditional ? arm_condition_codes[arm_current_cc] : "",
+ reg_name, &name[1]);
+ fprintf (asm_out_file, "\tldr%s\t%s, [%s, #- ((4 + . - %s) & 4095)]\n",
+ conditional ? arm_condition_codes[arm_current_cc] : "",
+ reg_name, reg_name, &name[1]);
+ arm_increase_location (8);
+ }
+ return ("");
+} /* arm_output_llc */
+
+
+/* Output code resembling an .lcomm directive. /bin/as doesn't have this
+ directive hence this hack, which works by reserving some `.space' in the
+ bss segment directly.
+
+ XXX This is a severe hack, which is guaranteed NOT to work since it doesn't
+ define STATIC COMMON space but merely STATIC BSS space. */
+
+void
+output_lcomm_directive (stream, name, size, rounded)
+ FILE *stream;
+ char *name;
+ int size, rounded;
+{
+ fputs ("\n\t.bss\t@ .lcomm\n", stream);
+ assemble_name (stream, name);
+ fprintf (stream, ":\t.space\t%d\n", rounded);
+ if (in_text_section ())
+ fputs ("\n\t.text\n", stream);
+ else
+ fputs ("\n\t.data\n", stream);
+} /* output_lcomm_directive */
+\f
+/* A finite state machine takes care of noticing whether or not instructions
+ can be conditionally executed, and thus decrease execution time and code
+ size by deleting branch instructions. The fsm is controlled by
+ final_prescan_insn, and controls the actions of ASM_OUTPUT_OPCODE. */
+
+/* The state of the fsm controlling condition codes are:
+ 0: normal, do nothing special
+ 1: make ASM_OUTPUT_OPCODE not output this instruction
+ 2: make ASM_OUTPUT_OPCODE not output this instruction
+ 3: make instructions conditional
+ 4: make instructions conditional
+
+ State transitions (state->state by whom under condition):
+ 0 -> 1 final_prescan_insn if the `target' is a label
+ 0 -> 2 final_prescan_insn if the `target' is an unconditional branch
+ 1 -> 3 ASM_OUTPUT_OPCODE after not having output the conditional branch
+ 2 -> 4 ASM_OUTPUT_OPCODE after not having output the conditional branch
+ 3 -> 0 ASM_OUTPUT_INTERNAL_LABEL if the `target' label is reached
+ (the target label has CODE_LABEL_NUMBER equal to arm_target_label).
+ 4 -> 0 final_prescan_insn if the `target' unconditional branch is reached
+ (the target insn is arm_target_insn).
+
+ XXX In case the `target' is an unconditional branch, this conditionalising
+ of the instructions always reduces code size, but not always execution
+ time. But then, I want to reduce the code size to somewhere near what
+ /bin/cc produces. */
+
+/* The condition codes of the ARM, and the inverse function. */
+char *arm_condition_codes[] =
+{
+ "eq", "ne", "cs", "cc", "mi", "pl", "vs", "vc",
+ "hi", "ls", "ge", "lt", "gt", "le", "al", "nv"
+};
+
+#define ARM_INVERSE_CONDITION_CODE(X) ((X) ^ 1)
+
+/* Returns the index of the ARM condition code string in
+ `arm_condition_codes'. COMPARISON should be an rtx like
+ `(eq (...) (...))'. */
+
+int
+get_arm_condition_code (comparison)
+ rtx comparison;
+{
+ switch (GET_CODE (comparison))
+ {
+ case NE: return (1);
+ case EQ: return (0);
+ case GE: return (10);
+ case GT: return (12);
+ case LE: return (13);
+ case LT: return (11);
+ case GEU: return (2);
+ case GTU: return (8);
+ case LEU: return (9);
+ case LTU: return (3);
+ default: abort ();
+ }
+ /*NOTREACHED*/
+ return (42);
+} /* get_arm_condition_code */
+
+
+void
+final_prescan_insn (insn, opvec, noperands)
+ rtx insn;
+ rtx *opvec;
+ int noperands;
+{
+ /* BODY will hold the body of INSN. */
+ register rtx body = PATTERN (insn);
+
+ /* This will be 1 if trying to repeat the trick, and things need to be
+ reversed if it appears to fail. */
+ int reverse = 0;
+
+ /* START_INSN will hold the insn from where we start looking. This is the
+ first insn after the following code_label if REVERSE is true. */
+ rtx start_insn = insn;
+
+ /* If in state 4, check if the target branch is reached, in order to
+ change back to state 0. */
+ if (arm_ccfsm_state == 4)
+ {
+ if (insn == arm_target_insn)
+ arm_ccfsm_state = 0;
+ return;
+ }
+
+ /* If in state 3, it is possible to repeat the trick, if this insn is an
+ unconditional branch to a label, and immediately following this branch
+ is the previous target label which is only used once, and the label this
+ branch jumps to is not too far off. */
+ if (arm_ccfsm_state == 3)
+ {
+ if (simplejump_p (insn))
+ {
+ start_insn = next_nonnote_insn (start_insn);
+ if (GET_CODE (start_insn) == BARRIER)
+ {
+ /* XXX Isn't this always a barrier? */
+ start_insn = next_nonnote_insn (start_insn);
+ }
+ if (GET_CODE (start_insn) == CODE_LABEL
+ && CODE_LABEL_NUMBER (start_insn) == arm_target_label
+ && LABEL_NUSES (start_insn) == 1)
+ reverse = TRUE;
+ else
+ return;
+ }
+ else
+ return;
+ }
+
+ if (arm_ccfsm_state != 0 && !reverse)
+ abort ();
+ if (GET_CODE (insn) != JUMP_INSN)
+ return;
+
+ if (reverse
+ || (GET_CODE (body) == SET && GET_CODE (SET_DEST (body)) == PC
+ && GET_CODE (SET_SRC (body)) == IF_THEN_ELSE))
+ {
+ int insns_skipped = 0, fail = FALSE, succeed = FALSE;
+ /* Flag which part of the IF_THEN_ELSE is the LABEL_REF. */
+ int then_not_else = TRUE;
+ rtx this_insn = start_insn, label;
+
+ /* Register the insn jumped to. */
+ if (reverse)
+ label = XEXP (SET_SRC (body), 0);
+ else if (GET_CODE (XEXP (SET_SRC (body), 1)) == LABEL_REF)
+ label = XEXP (XEXP (SET_SRC (body), 1), 0);
+ else if (GET_CODE (XEXP (SET_SRC (body), 2)) == LABEL_REF)
+ {
+ label = XEXP (XEXP (SET_SRC (body), 2), 0);
+ then_not_else = FALSE;
+ }
+ else
+ abort ();
+
+ /* See how many insns this branch skips, and what kind of insns. If all
+ insns are okay, and the label or unconditional branch to the same
+ label is not too far away, succeed. */
+ for (insns_skipped = 0;
+ !fail && !succeed && insns_skipped < MAX_INSNS_SKIPPED;
+ insns_skipped++)
+ {
+ rtx scanbody;
+
+ this_insn = next_nonnote_insn (this_insn);
+ if (!this_insn)
+ break;
+
+ scanbody = PATTERN (this_insn);
+
+ switch (GET_CODE (this_insn))
+ {
+ case CODE_LABEL:
+ /* Succeed if it is the target label, otherwise fail since
+ control falls in from somewhere else. */
+ if (this_insn == label)
+ {
+ arm_ccfsm_state = 1;
+ succeed = TRUE;
+ }
+ else
+ fail = TRUE;
+ break;
+
+ case BARRIER: /* XXX Is this case necessary? */
+ /* Succeed if the following insn is the target label.
+ Otherwise fail. */
+ this_insn = next_nonnote_insn (this_insn);
+ if (this_insn == label)
+ {
+ arm_ccfsm_state = 1;
+ succeed = TRUE;
+ }
+ else
+ fail = TRUE;
+ break;
+
+ case JUMP_INSN:
+ /* If this is an unconditional branch to the same label, succeed.
+ If it is to another label, do nothing. If it is conditional,
+ fail. */
+ /* XXX Probably, the test for the SET and the PC are unnecessary. */
+
+ if (GET_CODE (scanbody) == SET && GET_CODE (SET_DEST (scanbody)) == PC)
+ {
+ if (GET_CODE (SET_SRC (scanbody)) == LABEL_REF
+ && XEXP (SET_SRC (scanbody), 0) == label && !reverse)
+ {
+ arm_ccfsm_state = 2;
+ succeed = TRUE;
+ }
+ else if (GET_CODE (SET_SRC (scanbody)) == IF_THEN_ELSE)
+ fail = TRUE;
+ }
+ break;
+
+ case INSN:
+ /* Instructions affecting the condition codes make it fail. */
+ if (sets_cc0_p (scanbody))
+ fail = TRUE;
+ break;
+
+ default:
+ break;
+ }
+ }
+ if (succeed)
+ {
+ if (arm_ccfsm_state == 1 || reverse)
+ arm_target_label = CODE_LABEL_NUMBER (label);
+ else if (arm_ccfsm_state == 2)
+ arm_target_insn = this_insn;
+ else
+ abort ();
+
+ /* If REVERSE is true, ARM_CURRENT_CC needs to be inverted from what
+ it was. */
+ if (!reverse)
+ arm_current_cc = get_arm_condition_code (XEXP (SET_SRC (body), 0));
+ if (reverse || then_not_else)
+ arm_current_cc = ARM_INVERSE_CONDITION_CODE (arm_current_cc);
+ }
+ }
+} /* final_prescan_insn */
+
+/* EOF */
--- /dev/null
+/* Subroutines for insn-output.c for Tahoe.
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+
+This file is part of GNU CC.
+
+GNU CC is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU CC is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU CC; see the file COPYING. If not, write to
+the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+
+#include "config.h"
+#include "rtl.h"
+#include "regs.h"
+#include "hard-reg-set.h"
+#include "real.h"
+#include "insn-config.h"
+#include "conditions.h"
+#include "insn-flags.h"
+#include "output.h"
+#include "insn-attr.h"
+
+/*
+ * File: output-tahoe.c
+ *
+ * Original port made at the University of Buffalo by Devon Bowen,
+ * Dale Wiles and Kevin Zachmann.
+ *
+ * Changes for HCX by Piet van Oostrum,
+ * University of Utrecht, The Netherlands (piet@cs.ruu.nl)
+ *
+ * Speed tweaks by Michael Tiemann (tiemann@lurch.stanford.edu).
+ *
+ * Mail bugs reports or fixes to: gcc@cs.buffalo.edu
+ */
+
+
+/* On tahoe, you have to go to memory to convert a register
+ from sub-word to word. */
+
+rtx tahoe_reg_conversion_loc;
+
+int
+extendable_operand (op, mode)
+ rtx op;
+ enum machine_mode mode;
+{
+ if ((GET_CODE (op) == REG
+ || (GET_CODE (op) == SUBREG
+ && GET_CODE (SUBREG_REG (op)) == REG))
+ && tahoe_reg_conversion_loc == 0)
+ tahoe_reg_conversion_loc = assign_stack_local (SImode, GET_MODE_SIZE (SImode));
+ return general_operand (op, mode);
+}
+
+/* most of the print_operand_address function was taken from the vax */
+/* since the modes are basically the same. I had to add a special case, */
+/* though, for symbol references with offsets. */
+
+#include <stdio.h>
+
+print_operand_address (file, addr)
+ FILE *file;
+ register rtx addr;
+{
+ register rtx reg1, reg2, breg, ireg;
+ rtx offset;
+ static char *reg_name[] = REGISTER_NAMES;
+
+ retry:
+ switch (GET_CODE (addr))
+ {
+ case MEM:
+ fprintf (file, "*");
+ addr = XEXP (addr, 0);
+ goto retry;
+
+ case REG:
+ fprintf (file, "(%s)", reg_name [REGNO (addr)]);
+ break;
+
+ case PRE_DEC:
+ fprintf (file, "-(%s)", reg_name [REGNO (XEXP (addr, 0))]);
+ break;
+
+ case POST_INC:
+ fprintf (file, "(%s)+", reg_name [REGNO (XEXP (addr, 0))]);
+ break;
+
+ case PLUS:
+ reg1 = 0; reg2 = 0;
+ ireg = 0; breg = 0;
+ offset = 0;
+
+ if (CONSTANT_ADDRESS_P (XEXP (addr, 0))
+ && GET_CODE (XEXP (addr, 1)) == CONST_INT)
+ output_addr_const (file, addr);
+
+ if (CONSTANT_ADDRESS_P (XEXP (addr, 1))
+ && GET_CODE (XEXP (addr, 0)) == CONST_INT)
+ output_addr_const (file, addr);
+
+ if (CONSTANT_ADDRESS_P (XEXP (addr, 0))
+ || GET_CODE (XEXP (addr, 0)) == MEM)
+ {
+ offset = XEXP (addr, 0);
+ addr = XEXP (addr, 1);
+ }
+ else if (CONSTANT_ADDRESS_P (XEXP (addr, 1))
+ || GET_CODE (XEXP (addr, 1)) == MEM)
+ {
+ offset = XEXP (addr, 1);
+ addr = XEXP (addr, 0);
+ }
+ if (GET_CODE (addr) != PLUS)
+ ;
+ else if (GET_CODE (XEXP (addr, 0)) == MULT)
+ {
+ reg1 = XEXP (addr, 0);
+ addr = XEXP (addr, 1);
+ }
+ else if (GET_CODE (XEXP (addr, 1)) == MULT)
+ {
+ reg1 = XEXP (addr, 1);
+ addr = XEXP (addr, 0);
+ }
+ else if (GET_CODE (XEXP (addr, 0)) == REG)
+ {
+ reg1 = XEXP (addr, 0);
+ addr = XEXP (addr, 1);
+ }
+ else if (GET_CODE (XEXP (addr, 1)) == REG)
+ {
+ reg1 = XEXP (addr, 1);
+ addr = XEXP (addr, 0);
+ }
+ if (GET_CODE (addr) == REG || GET_CODE (addr) == MULT)
+ {
+ if (reg1 == 0)
+ reg1 = addr;
+ else
+ reg2 = addr;
+ addr = 0;
+ }
+ if (offset != 0)
+ {
+ if (addr != 0) abort ();
+ addr = offset;
+ }
+ if (reg1 != 0 && GET_CODE (reg1) == MULT)
+ {
+ breg = reg2;
+ ireg = reg1;
+ }
+ else if (reg2 != 0 && GET_CODE (reg2) == MULT)
+ {
+ breg = reg1;
+ ireg = reg2;
+ }
+ else if (reg2 != 0 || GET_CODE (addr) == MEM)
+ {
+ breg = reg2;
+ ireg = reg1;
+ }
+ else
+ {
+ breg = reg1;
+ ireg = reg2;
+ }
+ if (addr != 0)
+ output_address (offset);
+ if (breg != 0)
+ {
+ if (GET_CODE (breg) != REG)
+ abort ();
+ fprintf (file, "(%s)", reg_name[REGNO (breg)]);
+ }
+ if (ireg != 0)
+ {
+ if (GET_CODE (ireg) == MULT)
+ ireg = XEXP (ireg, 0);
+ if (GET_CODE (ireg) != REG)
+ abort ();
+ fprintf (file, "[%s]", reg_name[REGNO (ireg)]);
+ }
+ break;
+
+ default:
+ output_addr_const (file, addr);
+ }
+}
+
+/* Do a quick check and find out what the best way to do the */
+/* mini-move is. Could be a push or a move..... */
+
+static char *
+singlemove_string (operands)
+ rtx *operands;
+{
+ if (operands[1] == const0_rtx)
+ return "clrl %0";
+ if (push_operand (operands[0], SImode))
+ return "pushl %1";
+ return "movl %1,%0";
+}
+
+/* given the rtx for an address, return true if the given */
+/* register number is used in the address somewhere. */
+
+regisused(addr,regnum)
+rtx addr;
+int regnum;
+{
+ if (GET_CODE(addr) == REG)
+ if (REGNO(addr) == regnum)
+ return (1);
+ else
+ return (0);
+
+ if (GET_CODE(addr) == MEM)
+ return regisused(XEXP(addr,0),regnum);
+
+ if ((GET_CODE(addr) == MULT) || (GET_CODE(addr) == PLUS))
+ return ((regisused(XEXP(addr,0),regnum)) ||
+ (regisused(XEXP(addr,1),regnum)));
+
+ return 0;
+}
+
+
+/* Given some rtx, traverse it and return the register used in a */
+/* index. If no index is found, return 0. */
+
+rtx
+index_reg(addr)
+rtx addr;
+{
+ rtx temp;
+
+ if (GET_CODE(addr) == MEM)
+ return index_reg(XEXP(addr,0));
+
+ if (GET_CODE(addr) == MULT)
+ if (GET_CODE(XEXP(addr,0)) == REG)
+ return XEXP(addr,0);
+ else
+ return XEXP(addr,1);
+
+ if (GET_CODE(addr) == PLUS)
+ if (temp = index_reg(XEXP(addr,0)))
+ return temp;
+ else
+ return index_reg(XEXP(addr,1));
+
+ return 0;
+}
+
+
+/* simulate the move double by generating two movl's. You have */
+/* to be careful about mixing modes here. */
+
+char *
+output_move_double (operands)
+ rtx *operands;
+{
+ enum { REGOP, OFFSOP, MEMOP, PUSHOP, POPOP, INDOP, CNSTOP, RNDOP }
+ optype0, optype1;
+ rtx latehalf[2];
+ rtx shftreg0 = 0, shftreg1 = 0;
+ rtx temp0 = 0, temp1 = 0;
+ rtx addreg0 = 0, addreg1 = 0;
+ int dohighfirst = 0;
+
+ /* First classify both operands. */
+
+ if (REG_P (operands[0]))
+ optype0 = REGOP;
+ else if ((GET_CODE(operands[0])==MEM) && (shftreg0=index_reg(operands[0])))
+ optype0 = INDOP;
+ else if (offsettable_memref_p (operands[0]))
+ optype0 = OFFSOP;
+ else if (GET_CODE (XEXP (operands[0], 0)) == PRE_DEC) {
+ optype0 = PUSHOP;
+ dohighfirst++;
+ } else if (GET_CODE (operands[0]) == MEM)
+ optype0 = MEMOP;
+ else
+ optype0 = RNDOP;
+
+ if (REG_P (operands[1]))
+ optype1 = REGOP;
+ else if ((GET_CODE(operands[1])==MEM) && (shftreg1=index_reg(operands[1])))
+ optype1 = INDOP;
+ else if (offsettable_memref_p (operands[1]))
+ optype1 = OFFSOP;
+ else if (GET_CODE (XEXP (operands[1], 0)) == POST_INC)
+ optype1 = POPOP;
+ else if (GET_CODE (operands[1]) == MEM)
+ optype1 = MEMOP;
+ else if (CONSTANT_P (operands[1]))
+ optype1 = CNSTOP;
+ else
+ optype1 = RNDOP;
+
+ /* set up for the high byte move for operand zero */
+
+ switch (optype0) {
+
+ /* if it's a register, just use the next highest in the */
+ /* high address move. */
+
+ case REGOP : latehalf[0] = gen_rtx (REG,SImode,REGNO(operands[0])+1);
+ break;
+
+ /* for an offsettable address, use the gcc function to */
+ /* modify the operand to get an offset of 4 higher for */
+ /* the second move. */
+
+ case OFFSOP : latehalf[0] = adj_offsettable_operand (operands[0], 4);
+ break;
+
+ /* if the operand is MEMOP type, it must be a pointer */
+ /* to a pointer. So just remember to increase the mem */
+ /* location and use the same operand. */
+
+ case MEMOP : latehalf[0] = operands[0];
+ addreg0 = XEXP(operands[0],0);
+ break;
+
+ /* if we're dealing with a push instruction, just leave */
+ /* the operand alone since it auto-increments. */
+
+ case PUSHOP : latehalf[0] = operands[0];
+ break;
+
+ /* YUCK! Indexed addressing!! If the address is considered */
+ /* offsettable, go use the offset in the high part. Otherwise */
+ /* find what exactly is being added to the multiplication. If */
+ /* it's a mem reference, increment that with the high part */
+ /* being unchanged to cause the shift. If it's a reg, do the */
+ /* same. If you can't identify it, abort. Remember that the */
+ /* shift register was already set during identification. */
+
+ case INDOP : if (offsettable_memref_p(operands[0])) {
+ latehalf[0] = adj_offsettable_operand(operands[0],4);
+ break;
+ }
+
+ latehalf[0] = operands[0];
+
+ temp0 = XEXP(XEXP(operands[0],0),0);
+ if (GET_CODE(temp0) == MULT) {
+ temp1 = temp0;
+ temp0 = XEXP(XEXP(operands[0],0),1);
+ } else {
+ temp1 = XEXP(XEXP(operands[0],0),1);
+ if (GET_CODE(temp1) != MULT)
+ abort();
+ }
+
+ if (GET_CODE(temp0) == MEM)
+ addreg0 = temp0;
+ else if (GET_CODE(temp0) == REG)
+ addreg0 = temp0;
+ else
+ abort();
+
+ break;
+
+ /* if we don't know the operand type, print a friendly */
+ /* little error message... 8-) */
+
+ case RNDOP :
+ default : abort();
+ }
+
+ /* do the same setup for operand one */
+
+ switch (optype1) {
+
+ case REGOP : latehalf[1] = gen_rtx(REG,SImode,REGNO(operands[1])+1);
+ break;
+
+ case OFFSOP : latehalf[1] = adj_offsettable_operand (operands[1], 4);
+ break;
+
+ case MEMOP : latehalf[1] = operands[1];
+ addreg1 = XEXP(operands[1],0);
+ break;
+
+ case POPOP : latehalf[1] = operands[1];
+ break;
+
+ case INDOP : if (offsettable_memref_p(operands[1])) {
+ latehalf[1] = adj_offsettable_operand(operands[1],4);
+ break;
+ }
+
+ latehalf[1] = operands[1];
+
+ temp0 = XEXP(XEXP(operands[1],0),0);
+ if (GET_CODE(temp0) == MULT) {
+ temp1 = temp0;
+ temp0 = XEXP(XEXP(operands[1],0),1);
+ } else {
+ temp1 = XEXP(XEXP(operands[1],0),1);
+ if (GET_CODE(temp1) != MULT)
+ abort();
+ }
+
+ if (GET_CODE(temp0) == MEM)
+ addreg1 = temp0;
+ else if (GET_CODE(temp0) == REG)
+ addreg1 = temp0;
+ else
+ abort();
+
+ break;
+
+ case CNSTOP :
+ if (GET_CODE (operands[1]) == CONST_DOUBLE)
+ split_double (operands[1], &operands[1], &latehalf[1]);
+ else if (CONSTANT_P (operands[1]))
+ latehalf[1] = const0_rtx;
+ else abort ();
+ break;
+
+ case RNDOP :
+ default : abort();
+ }
+
+
+ /* double the register used for shifting in both of the operands */
+ /* but make sure the same register isn't doubled twice! */
+
+ if (shftreg0 && shftreg1 && (rtx_equal_p(shftreg0,shftreg1)))
+ output_asm_insn("addl2 %0,%0", &shftreg0);
+ else {
+ if (shftreg0)
+ output_asm_insn("addl2 %0,%0", &shftreg0);
+ if (shftreg1)
+ output_asm_insn("addl2 %0,%0", &shftreg1);
+ }
+
+ /* if the destination is a register and that register is needed in */
+ /* the source addressing mode, swap the order of the moves since we */
+ /* don't want this destroyed til last. If both regs are used, not */
+ /* much we can do, so abort. If these becomes a problem, maybe we */
+ /* can do it on the stack? */
+
+ if (GET_CODE(operands[0])==REG && regisused(operands[1],REGNO(operands[0])))
+ if (regisused(latehalf[1],REGNO(latehalf[0])))
+ 8;
+ else
+ dohighfirst++;
+
+ /* if we're pushing, do the high address part first. */
+
+ if (dohighfirst) {
+
+ if (addreg0 && addreg1 && (rtx_equal_p(addreg0,addreg1)))
+ output_asm_insn("addl2 $4,%0", &addreg0);
+ else {
+ if (addreg0)
+ output_asm_insn("addl2 $4,%0", &addreg0);
+ if (addreg1)
+ output_asm_insn("addl2 $4,%0", &addreg1);
+ }
+
+ output_asm_insn(singlemove_string(latehalf), latehalf);
+
+ if (addreg0 && addreg1 && (rtx_equal_p(addreg0,addreg1)))
+ output_asm_insn("subl2 $4,%0", &addreg0);
+ else {
+ if (addreg0)
+ output_asm_insn("subl2 $4,%0", &addreg0);
+ if (addreg1)
+ output_asm_insn("subl2 $4,%0", &addreg1);
+ }
+
+ return singlemove_string(operands);
+ }
+
+ output_asm_insn(singlemove_string(operands), operands);
+
+ if (addreg0 && addreg1 && (rtx_equal_p(addreg0,addreg1)))
+ output_asm_insn("addl2 $4,%0", &addreg0);
+ else {
+ if (addreg0)
+ output_asm_insn("addl2 $4,%0", &addreg0);
+ if (addreg1)
+ output_asm_insn("addl2 $4,%0", &addreg1);
+ }
+
+ output_asm_insn(singlemove_string(latehalf), latehalf);
+
+ if (addreg0 && addreg1 && (rtx_equal_p(addreg0,addreg1)))
+ output_asm_insn("subl2 $4,%0", &addreg0);
+ else {
+ if (addreg0)
+ output_asm_insn("subl2 $4,%0", &addreg0);
+ if (addreg1)
+ output_asm_insn("subl2 $4,%0", &addreg1);
+ }
+
+ if (shftreg0 && shftreg1 && (rtx_equal_p(shftreg0,shftreg1)))
+ output_asm_insn("shar $1,%0,%0", &shftreg0);
+ else {
+ if (shftreg0)
+ output_asm_insn("shar $1,%0,%0", &shftreg0);
+ if (shftreg1)
+ output_asm_insn("shar $1,%0,%0", &shftreg1);
+ }
+
+ return "";
+}
+
+
+/* This checks if a zero_extended cmp[bw] can be replaced by a sign_extended
+ cmp[bw]. This can be done if the operand is a constant that fits in a
+ byte/word or a memory operand. Besides that the next instruction must be an
+ unsigned compare. Some of these tests are done by the machine description */
+
+int
+tahoe_cmp_check (insn, op, max)
+rtx insn, op; int max;
+{
+ if (GET_CODE (op) == CONST_INT
+ && ( INTVAL (op) < 0 || INTVAL (op) > max ))
+ return 0;
+ {
+ register rtx next = NEXT_INSN (insn);
+
+ if ((GET_CODE (next) == JUMP_INSN
+ || GET_CODE (next) == INSN
+ || GET_CODE (next) == CALL_INSN))
+ {
+ next = PATTERN (next);
+ if (GET_CODE (next) == SET
+ && SET_DEST (next) == pc_rtx
+ && GET_CODE (SET_SRC (next)) == IF_THEN_ELSE)
+ switch (GET_CODE (XEXP (SET_SRC (next), 0)))
+ {
+ case EQ:
+ case NE:
+ case LTU:
+ case GTU:
+ case LEU:
+ case GEU:
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
--- /dev/null
+/* Dummy data flow analysis for GNU compiler in nonoptimizing mode.
+ Copyright (C) 1987, 1991 Free Software Foundation, Inc.
+
+This file is part of GNU CC.
+
+GNU CC is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU CC is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU CC; see the file COPYING. If not, write to
+the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+
+/* This file performs stupid register allocation, which is used
+ when cc1 gets the -noreg switch (which is when cc does not get -O).
+
+ Stupid register allocation goes in place of the the flow_analysis,
+ local_alloc and global_alloc passes. combine_instructions cannot
+ be done with stupid allocation because the data flow info that it needs
+ is not computed here.
+
+ In stupid allocation, the only user-defined variables that can
+ go in registers are those declared "register". They are assumed
+ to have a life span equal to their scope. Other user variables
+ are given stack slots in the rtl-generation pass and are not
+ represented as pseudo regs. A compiler-generated temporary
+ is assumed to live from its first mention to its last mention.
+
+ Since each pseudo-reg's life span is just an interval, it can be
+ represented as a pair of numbers, each of which identifies an insn by
+ its position in the function (number of insns before it). The first
+ thing done for stupid allocation is to compute such a number for each
+ insn. It is called the suid. Then the life-interval of each
+ pseudo reg is computed. Then the pseudo regs are ordered by priority
+ and assigned hard regs in priority order. */
+
+#include <stdio.h>
+#include "config.h"
+#include "rtl.h"
+#include "hard-reg-set.h"
+#include "regs.h"
+#include "flags.h"
+\f
+/* Vector mapping INSN_UIDs to suids.
+ The suids are like uids but increase monotonically always.
+ We use them to see whether a subroutine call came
+ between a variable's birth and its death. */
+
+static int *uid_suid;
+
+/* Get the suid of an insn. */
+
+#define INSN_SUID(INSN) (uid_suid[INSN_UID (INSN)])
+
+/* Record the suid of the last CALL_INSN
+ so we can tell whether a pseudo reg crosses any calls. */
+
+static int last_call_suid;
+
+/* Record the suid of the last JUMP_INSN
+ so we can tell whether a pseudo reg crosses any jumps. */
+
+static int last_jump_suid;
+
+/* Record the suid of the last CODE_LABEL
+ so we can tell whether a pseudo reg crosses any labels. */
+
+static int last_label_suid;
+
+/* Element N is suid of insn where life span of pseudo reg N ends.
+ Element is 0 if register N has not been seen yet on backward scan. */
+
+static int *reg_where_dead;
+
+/* Element N is suid of insn where life span of pseudo reg N begins. */
+
+static int *reg_where_born;
+
+/* Element N is 1 if pseudo reg N lives across labels or jumps. */
+
+static char *reg_crosses_blocks;
+
+/* Numbers of pseudo-regs to be allocated, highest priority first. */
+
+static int *reg_order;
+
+/* Indexed by reg number (hard or pseudo), nonzero if register is live
+ at the current point in the instruction stream. */
+
+static char *regs_live;
+
+/* Indexed by insn's suid, the set of hard regs live after that insn. */
+
+static HARD_REG_SET *after_insn_hard_regs;
+
+/* Record that hard reg REGNO is live after insn INSN. */
+
+#define MARK_LIVE_AFTER(INSN,REGNO) \
+ SET_HARD_REG_BIT (after_insn_hard_regs[INSN_SUID (INSN)], (REGNO))
+
+static void stupid_mark_refs ();
+static int stupid_reg_compare ();
+static int stupid_find_reg ();
+\f
+/* Stupid life analysis is for the case where only variables declared
+ `register' go in registers. For this case, we mark all
+ pseudo-registers that belong to register variables as
+ dying in the last instruction of the function, and all other
+ pseudo registers as dying in the last place they are referenced.
+ Hard registers are marked as dying in the last reference before
+ the end or before each store into them. */
+
+void
+stupid_life_analysis (f, nregs, file)
+ rtx f;
+ int nregs;
+ FILE *file;
+{
+ register int i;
+ register rtx last, insn;
+ int max_uid;
+
+ bzero (regs_ever_live, sizeof regs_ever_live);
+
+ regs_live = (char *) alloca (nregs);
+
+ /* First find the last real insn, and count the number of insns,
+ and assign insns their suids. */
+
+ for (insn = f, i = 0; insn; insn = NEXT_INSN (insn))
+ if (INSN_UID (insn) > i)
+ i = INSN_UID (insn);
+
+ max_uid = i + 1;
+ uid_suid = (int *) alloca ((i + 1) * sizeof (int));
+
+ /* Compute the mapping from uids to suids.
+ Suids are numbers assigned to insns, like uids,
+ except that suids increase monotonically through the code. */
+
+ last = 0; /* In case of empty function body */
+ for (insn = f, i = 0; insn; insn = NEXT_INSN (insn))
+ {
+ if (GET_CODE (insn) == INSN || GET_CODE (insn) == CALL_INSN
+ || GET_CODE (insn) == JUMP_INSN)
+ last = insn;
+ INSN_SUID (insn) = ++i;
+ }
+
+ last_call_suid = i + 1;
+ last_jump_suid = i + 1;
+ last_label_suid = i + 1;
+
+ max_regno = nregs;
+
+ /* Allocate tables to record info about regs. */
+
+ reg_where_dead = (int *) alloca (nregs * sizeof (int));
+ bzero (reg_where_dead, nregs * sizeof (int));
+
+ reg_where_born = (int *) alloca (nregs * sizeof (int));
+ bzero (reg_where_born, nregs * sizeof (int));
+
+ reg_crosses_blocks = (char *) alloca (nregs);
+ bzero (reg_crosses_blocks, nregs);
+
+ reg_order = (int *) alloca (nregs * sizeof (int));
+ bzero (reg_order, nregs * sizeof (int));
+
+ reg_renumber = (short *) oballoc (nregs * sizeof (short));
+ for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
+ reg_renumber[i] = i;
+
+ for (i = FIRST_VIRTUAL_REGISTER; i <= LAST_VIRTUAL_REGISTER; i++)
+ reg_renumber[i] = -1;
+
+ after_insn_hard_regs = (HARD_REG_SET *) alloca (max_uid * sizeof (HARD_REG_SET));
+ bzero (after_insn_hard_regs, max_uid * sizeof (HARD_REG_SET));
+
+ /* Allocate and zero out many data structures
+ that will record the data from lifetime analysis. */
+
+ allocate_for_life_analysis ();
+
+ for (i = 0; i < max_regno; i++)
+ {
+ reg_n_deaths[i] = 1;
+ }
+
+ bzero (regs_live, nregs);
+
+ /* Find where each pseudo register is born and dies,
+ by scanning all insns from the end to the start
+ and noting all mentions of the registers.
+
+ Also find where each hard register is live
+ and record that info in after_insn_hard_regs.
+ regs_live[I] is 1 if hard reg I is live
+ at the current point in the scan. */
+
+ for (insn = last; insn; insn = PREV_INSN (insn))
+ {
+ register HARD_REG_SET *p = after_insn_hard_regs + INSN_SUID (insn);
+
+ /* Copy the info in regs_live
+ into the element of after_insn_hard_regs
+ for the current position in the rtl code. */
+
+ for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
+ if (regs_live[i])
+ SET_HARD_REG_BIT (*p, i);
+
+ /* Mark all call-clobbered regs as live after each call insn
+ so that a pseudo whose life span includes this insn
+ will not go in one of them.
+ Then mark those regs as all dead for the continuing scan
+ of the insns before the call. */
+
+ if (GET_CODE (insn) == CALL_INSN)
+ {
+ last_call_suid = INSN_SUID (insn);
+ IOR_HARD_REG_SET (after_insn_hard_regs[last_call_suid],
+ call_used_reg_set);
+ for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
+ if (call_used_regs[i])
+ regs_live[i] = 0;
+ }
+
+ if (GET_CODE (insn) == JUMP_INSN)
+ last_jump_suid = INSN_SUID (insn);
+
+ if (GET_CODE (insn) == CODE_LABEL)
+ last_label_suid = INSN_SUID (insn);
+
+ /* Update which hard regs are currently live
+ and also the birth and death suids of pseudo regs
+ based on the pattern of this insn. */
+
+ if (GET_CODE (insn) == INSN
+ || GET_CODE (insn) == CALL_INSN
+ || GET_CODE (insn) == JUMP_INSN)
+ {
+ stupid_mark_refs (PATTERN (insn), insn);
+ }
+ }
+
+ /* Now decide the order in which to allocate the pseudo registers. */
+
+ for (i = LAST_VIRTUAL_REGISTER + 1; i < max_regno; i++)
+ reg_order[i] = i;
+
+ qsort (®_order[LAST_VIRTUAL_REGISTER + 1],
+ max_regno - LAST_VIRTUAL_REGISTER - 1, sizeof (int),
+ stupid_reg_compare);
+
+ /* Now, in that order, try to find hard registers for those pseudo regs. */
+
+ for (i = LAST_VIRTUAL_REGISTER + 1; i < max_regno; i++)
+ {
+ register int r = reg_order[i];
+ enum reg_class class;
+
+ /* Some regnos disappear from the rtl. Ignore them to avoid crash. */
+ if (regno_reg_rtx[r] == 0)
+ continue;
+
+ /* Now find the best hard-register class for this pseudo register */
+ if (N_REG_CLASSES > 1)
+ {
+ class = reg_preferred_class (r);
+
+ reg_renumber[r] = stupid_find_reg (reg_n_calls_crossed[r], class,
+ PSEUDO_REGNO_MODE (r),
+ reg_where_born[r],
+ reg_where_dead[r],
+ reg_crosses_blocks[r]);
+ }
+ else
+ reg_renumber[r] = -1;
+
+ /* If no reg available in that class,
+ try any reg. */
+ if (reg_renumber[r] == -1)
+ reg_renumber[r] = stupid_find_reg (reg_n_calls_crossed[r],
+ GENERAL_REGS,
+ PSEUDO_REGNO_MODE (r),
+ reg_where_born[r],
+ reg_where_dead[r],
+ reg_crosses_blocks[r]);
+ }
+
+ if (file)
+ dump_flow_info (file);
+}
+
+/* Comparison function for qsort.
+ Returns -1 (1) if register *R1P is higher priority than *R2P. */
+
+static int
+stupid_reg_compare (r1p, r2p)
+ int *r1p, *r2p;
+{
+ register int r1 = *r1p, r2 = *r2p;
+ register int len1 = reg_where_dead[r1] - reg_where_born[r1];
+ register int len2 = reg_where_dead[r2] - reg_where_born[r2];
+ int tem;
+
+ tem = len2 - len1;
+ if (tem != 0) return tem;
+
+ tem = reg_n_refs[r1] - reg_n_refs[r2];
+ if (tem != 0) return tem;
+
+ /* If regs are equally good, sort by regno,
+ so that the results of qsort leave nothing to chance. */
+ return r1 - r2;
+}
+\f
+/* Find a block of SIZE words of hard registers in reg_class CLASS
+ that can hold a value of machine-mode MODE
+ (but actually we test only the first of the block for holding MODE)
+ currently free from after insn whose suid is BIRTH
+ through the insn whose suid is DEATH,
+ and return the number of the first of them.
+ Return -1 if such a block cannot be found.
+
+ If CALL_PRESERVED is nonzero, insist on registers preserved
+ over subroutine calls, and return -1 if cannot find such.
+ If CROSSES_BLOCKS is nonzero, reject registers for which
+ PRESERVE_DEATH_INFO_REGNO_P is true. */
+
+static int
+stupid_find_reg (call_preserved, class, mode,
+ born_insn, dead_insn, crosses_blocks)
+ int call_preserved;
+ enum reg_class class;
+ enum machine_mode mode;
+ int born_insn, dead_insn;
+ int crosses_blocks;
+{
+ register int i, ins;
+#ifdef HARD_REG_SET
+ register /* Declare them register if they are scalars. */
+#endif
+ HARD_REG_SET used, this_reg;
+#ifdef ELIMINABLE_REGS
+ static struct {int from, to; } eliminables[] = ELIMINABLE_REGS;
+#endif
+
+ COPY_HARD_REG_SET (used,
+ call_preserved ? call_used_reg_set : fixed_reg_set);
+
+#ifdef ELIMINABLE_REGS
+ for (i = 0; i < sizeof eliminables / sizeof eliminables[0]; i++)
+ SET_HARD_REG_BIT (used, eliminables[i].from);
+#else
+ SET_HARD_REG_BIT (used, FRAME_POINTER_REGNUM);
+#endif
+
+ for (ins = born_insn; ins < dead_insn; ins++)
+ IOR_HARD_REG_SET (used, after_insn_hard_regs[ins]);
+
+ IOR_COMPL_HARD_REG_SET (used, reg_class_contents[(int) class]);
+
+ for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
+ {
+#ifdef REG_ALLOC_ORDER
+ int regno = reg_alloc_order[i];
+#else
+ int regno = i;
+#endif
+
+ /* If we need reasonable death info on this hard reg,
+ don't use it for anything whose life spans a label or a jump. */
+#ifdef PRESERVE_DEATH_INFO_REGNO_P
+ if (PRESERVE_DEATH_INFO_REGNO_P (regno)
+ && crosses_blocks)
+ continue;
+#endif
+ /* If a register has screwy overlap problems,
+ don't use it at all if not optimizing.
+ Actually this is only for the 387 stack register,
+ and it's because subsequent code won't work. */
+#ifdef OVERLAPPING_REGNO_P
+ if (OVERLAPPING_REGNO_P (regno))
+ continue;
+#endif
+
+ if (! TEST_HARD_REG_BIT (used, regno)
+ && HARD_REGNO_MODE_OK (regno, mode))
+ {
+ register int j;
+ register int size1 = HARD_REGNO_NREGS (regno, mode);
+ for (j = 1; j < size1 && ! TEST_HARD_REG_BIT (used, regno + j); j++);
+ if (j == size1)
+ {
+ CLEAR_HARD_REG_SET (this_reg);
+ while (--j >= 0)
+ SET_HARD_REG_BIT (this_reg, regno + j);
+ for (ins = born_insn; ins < dead_insn; ins++)
+ {
+ IOR_HARD_REG_SET (after_insn_hard_regs[ins], this_reg);
+ }
+ return regno;
+ }
+#ifndef REG_ALLOC_ORDER
+ i += j; /* Skip starting points we know will lose */
+#endif
+ }
+ }
+ return -1;
+}
+\f
+/* Walk X, noting all assignments and references to registers
+ and recording what they imply about life spans.
+ INSN is the current insn, supplied so we can find its suid. */
+
+static void
+stupid_mark_refs (x, insn)
+ rtx x, insn;
+{
+ register RTX_CODE code = GET_CODE (x);
+ register char *fmt;
+ register int regno, i;
+
+ if (code == SET || code == CLOBBER)
+ {
+ if (SET_DEST (x) != 0 && GET_CODE (SET_DEST (x)) == REG)
+ {
+ /* Register is being assigned. */
+ regno = REGNO (SET_DEST (x));
+
+ /* For hard regs, update the where-live info. */
+ if (regno < FIRST_PSEUDO_REGISTER)
+ {
+ register int j
+ = HARD_REGNO_NREGS (regno, GET_MODE (SET_DEST (x)));
+ while (--j >= 0)
+ {
+ regs_ever_live[regno+j] = 1;
+ regs_live[regno+j] = 0;
+ /* The following line is for unused outputs;
+ they do get stored even though never used again. */
+ MARK_LIVE_AFTER (insn, regno);
+ /* When a hard reg is clobbered, mark it in use
+ just before this insn, so it is live all through. */
+ if (code == CLOBBER && INSN_SUID (insn) > 0)
+ SET_HARD_REG_BIT (after_insn_hard_regs[INSN_SUID (insn) - 1],
+ regno);
+ }
+ }
+ /* For pseudo regs, record where born, where dead, number of
+ times used, and whether live across a call. */
+ else
+ {
+ /* Update the life-interval bounds of this pseudo reg. */
+
+ /* When a pseudo-reg is CLOBBERed, it is born just before
+ the clobbering insn. When setting, just after. */
+ int where_born = INSN_SUID (insn) - (code == CLOBBER);
+
+ reg_where_born[regno] = where_born;
+ /* The reg must live at least one insn even
+ in it is never again used--because it has to go
+ in SOME hard reg. Mark it as dying after the current
+ insn so that it will conflict with any other outputs of
+ this insn. */
+ if (reg_where_dead[regno] < where_born + 2)
+ reg_where_dead[regno] = where_born + 2;
+
+ /* Count the refs of this reg. */
+ reg_n_refs[regno]++;
+
+ if (last_call_suid < reg_where_dead[regno])
+ reg_n_calls_crossed[regno] += 1;
+ if (last_jump_suid < reg_where_dead[regno]
+ || last_label_suid < reg_where_dead[regno])
+ reg_crosses_blocks[regno] = 1;
+ }
+ }
+ /* Record references from the value being set,
+ or from addresses in the place being set if that's not a reg.
+ If setting a SUBREG, we treat the entire reg as *used*. */
+ if (code == SET)
+ {
+ stupid_mark_refs (SET_SRC (x), insn);
+ if (GET_CODE (SET_DEST (x)) != REG)
+ stupid_mark_refs (SET_DEST (x), insn);
+ }
+ return;
+ }
+
+ /* Register value being used, not set. */
+
+ if (code == REG)
+ {
+ regno = REGNO (x);
+ if (regno < FIRST_PSEUDO_REGISTER)
+ {
+ /* Hard reg: mark it live for continuing scan of previous insns. */
+ register int j = HARD_REGNO_NREGS (regno, GET_MODE (x));
+ while (--j >= 0)
+ {
+ regs_ever_live[regno+j] = 1;
+ regs_live[regno+j] = 1;
+ }
+ }
+ else
+ {
+ /* Pseudo reg: record first use, last use and number of uses. */
+
+ reg_where_born[regno] = INSN_SUID (insn);
+ reg_n_refs[regno]++;
+ if (regs_live[regno] == 0)
+ {
+ regs_live[regno] = 1;
+ reg_where_dead[regno] = INSN_SUID (insn);
+ }
+ }
+ return;
+ }
+
+ /* Recursive scan of all other rtx's. */
+
+ fmt = GET_RTX_FORMAT (code);
+ for (i = GET_RTX_LENGTH (code) - 1; i >= 0; i--)
+ {
+ if (fmt[i] == 'e')
+ stupid_mark_refs (XEXP (x, i), insn);
+ if (fmt[i] == 'E')
+ {
+ register int j;
+ for (j = XVECLEN (x, i) - 1; j >= 0; j--)
+ stupid_mark_refs (XVECEXP (x, i, j), insn);
+ }
+ }
+}