diff options
author | Richard Kenner <kenner@gcc.gnu.org> | 1992-01-02 21:35:26 -0500 |
---|---|---|
committer | Richard Kenner <kenner@gcc.gnu.org> | 1992-01-02 21:35:26 -0500 |
commit | e6ee5154b7a515f17fdba2042e8b0873aa11c706 (patch) | |
tree | 8675f8ae284752d298652a00580ed9dc2e3f1ab1 /gcc/config/romp | |
parent | 9f682aa750be6502cf322245d9a0cb05470d302d (diff) | |
download | gcc-e6ee5154b7a515f17fdba2042e8b0873aa11c706.zip gcc-e6ee5154b7a515f17fdba2042e8b0873aa11c706.tar.gz gcc-e6ee5154b7a515f17fdba2042e8b0873aa11c706.tar.bz2 |
Initial revision
From-SVN: r154
Diffstat (limited to 'gcc/config/romp')
-rw-r--r-- | gcc/config/romp/romp.c | 1898 |
1 files changed, 1898 insertions, 0 deletions
diff --git a/gcc/config/romp/romp.c b/gcc/config/romp/romp.c new file mode 100644 index 0000000..8b72b57 --- /dev/null +++ b/gcc/config/romp/romp.c @@ -0,0 +1,1898 @@ +/* Subroutines used for code generation on ROMP. + Copyright (C) 1987-1991 Free Software Foundation, Inc. + Contributed by Richard Kenner (kenner@nyu.edu) + +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 "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" +#include "recog.h" +#include "expr.h" +#include "obstack.h" +#include "tree.h" + +#define min(A,B) ((A) < (B) ? (A) : (B)) +#define max(A,B) ((A) > (B) ? (A) : (B)) + +static int unsigned_comparisons_p (); +static void output_loadsave_fpregs (); +static void output_fpops (); +static void init_fpops (); + +/* Return 1 if the insn using CC0 set by INSN does not contain + any unsigned tests applied to the condition codes. + + Based on `next_insn_tests_no_inequality' in recog.c. */ + +int +next_insn_tests_no_unsigned (insn) + rtx insn; +{ + register rtx next = next_cc0_user (insn); + + if (next == 0) + { + if (find_reg_note (insn, REG_UNUSED, cc0_rtx)) + return 1; + else + abort (); + } + + return ((GET_CODE (next) == JUMP_INSN + || GET_CODE (next) == INSN + || GET_CODE (next) == CALL_INSN) + && ! unsigned_comparisons_p (PATTERN (next))); +} + +static int +unsigned_comparisons_p (x) + rtx x; +{ + register char *fmt; + register int len, i; + register enum rtx_code code = GET_CODE (x); + + switch (code) + { + case REG: + case PC: + case CC0: + case CONST_INT: + case CONST_DOUBLE: + case CONST: + case LABEL_REF: + case SYMBOL_REF: + return 0; + + case LTU: + case GTU: + case LEU: + case GEU: + return (XEXP (x, 0) == cc0_rtx || XEXP (x, 1) == cc0_rtx); + } + + len = GET_RTX_LENGTH (code); + fmt = GET_RTX_FORMAT (code); + + for (i = 0; i < len; i++) + { + if (fmt[i] == 'e') + { + if (unsigned_comparisons_p (XEXP (x, i))) + return 1; + } + else if (fmt[i] == 'E') + { + register int j; + for (j = XVECLEN (x, i) - 1; j >= 0; j--) + if (unsigned_comparisons_p (XVECEXP (x, i, j))) + return 1; + } + } + + return 0; +} + +/* Update the condition code from the insn. Look mostly at the first + byte of the machine-specific insn description information. + + cc_state.value[12] refer to two possible values that might correspond + to the CC. We only store register values. */ + +update_cc (body, insn) + rtx body; + rtx insn; +{ + switch (get_attr_cc (insn)) + { + case CC_NONE: + /* Insn does not affect the CC at all. */ + break; + + case CC_CHANGE0: + /* Insn doesn't affect the CC but does modify operand[0], known to be + a register. */ + if (cc_status.value1 != 0 + && reg_overlap_mentioned_p (recog_operand[0], cc_status.value1)) + cc_status.value1 = 0; + + if (cc_status.value2 != 0 + && reg_overlap_mentioned_p (recog_operand[0], cc_status.value2)) + cc_status.value2 = 0; + + break; + + case CC_COPY1TO0: + /* Insn copies operand[1] to operand[0], both registers, but doesn't + affect the CC. */ + if (cc_status.value1 != 0 + && reg_overlap_mentioned_p (recog_operand[0], cc_status.value1)) + cc_status.value1 = 0; + + if (cc_status.value2 != 0 + && reg_overlap_mentioned_p (recog_operand[0], cc_status.value2)) + cc_status.value2 = 0; + + if (cc_status.value1 != 0 + && rtx_equal_p (cc_status.value1, recog_operand[1])) + cc_status.value2 = recog_operand[0]; + + if (cc_status.value2 != 0 + && rtx_equal_p (cc_status.value2, recog_operand[1])) + cc_status.value1 = recog_operand[0]; + + break; + + case CC_CLOBBER: + /* Insn clobbers CC. */ + CC_STATUS_INIT; + break; + + case CC_SETS: + /* Insn sets CC to recog_operand[0], but overflow is impossible. */ + CC_STATUS_INIT; + cc_status.flags |= CC_NO_OVERFLOW; + cc_status.value1 = recog_operand[0]; + break; + + case CC_COMPARE: + /* Insn is a compare which sets the CC fully. Update CC_STATUS for this + compare and mark whether the test will be signed or unsigned. */ + { + register rtx p = PATTERN (insn); + + CC_STATUS_INIT; + + if (GET_CODE (p) == PARALLEL) + p = XVECEXP (p, 0, 0); + cc_status.value1 = SET_SRC (p); + + if (GET_CODE (SET_SRC (p)) == REG) + cc_status.flags |= CC_NO_OVERFLOW; + if (! next_insn_tests_no_unsigned (insn)) + cc_status.flags |= CC_UNSIGNED; + } + break; + + case CC_TBIT: + /* Insn sets T bit if result is non-zero. Next insn must be branch. */ + CC_STATUS_INIT; + cc_status.flags = CC_IN_TB | CC_NOT_NEGATIVE; + break; + + default: + abort (); + } +} + +/* Return 1 if a previous compare needs to be re-issued. This will happen + if two compares tested the same objects, but one was signed and the + other unsigned. OP is the comparison operation being performed. */ + +int +restore_compare_p (op) + rtx op; +{ + enum rtx_code code = GET_CODE (op); + + return (((code == GEU || code == LEU || code == GTU || code == LTU) + && ! (cc_status.flags & CC_UNSIGNED)) + || ((code == GE || code == LE || code == GT || code == LT) + && (cc_status.flags & CC_UNSIGNED))); +} + +/* Generate the (long) string corresponding to an inline multiply insn. + Note that `r10' does not refer to the register r10, but rather to the + SCR used as the MQ. */ +char * +output_in_line_mul () +{ + static char insns[200]; + int i; + + strcpy (insns, "s %0,%0\n"); + strcat (insns, "\tmts r10,%1\n"); + for (i = 0; i < 16; i++) + strcat (insns, "\tm %0,%2\n"); + strcat (insns, "\tmfs r10,%0"); + + return insns; +} + +/* Returns 1 if OP is a memory reference with an offset from a register within + the range specified. The offset must also be a multiple of the size of the + mode. */ + +static int +memory_offset_in_range_p (op, mode, low, high) + register rtx op; + enum machine_mode mode; + int low, high; +{ + int offset = 0; + + if (! memory_operand (op, mode)) + return 0; + + while (GET_CODE (op) == SUBREG) + { + offset += SUBREG_WORD (op) * UNITS_PER_WORD; +#if BYTES_BIG_ENDIAN + offset -= (min (UNITS_PER_WORD, GET_MODE_SIZE (GET_MODE (op))) + - min (UNITS_PER_WORD, + GET_MODE_SIZE (GET_MODE (SUBREG_REG (op))))); +#endif + op = SUBREG_REG (op); + } + + /* We must now have either (mem (reg (x)), (mem (plus (reg (x)) (c))), + or a constant pool address. */ + if (GET_CODE (op) != MEM) + abort (); + + /* Now use the actual mode and get the address. */ + mode = GET_MODE (op); + op = XEXP (op, 0); + if (GET_CODE (op) == SYMBOL_REF && CONSTANT_POOL_ADDRESS_P (op)) + offset = get_pool_offset (op) + 12; + else if (GET_CODE (op) == PLUS) + { + if (GET_CODE (XEXP (op, 1)) != CONST_INT + || ! register_operand (XEXP (op, 0), Pmode)) + return 0; + + offset += INTVAL (XEXP (op, 1)); + } + + else if (! register_operand (op, Pmode)) + return 0; + + return (offset >= low && offset <= high + && (offset % GET_MODE_SIZE (mode) == 0)); +} + +/* Return 1 if OP is a valid operand for a memory reference insn that can + only reference indirect through a register. */ + +int +zero_memory_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return memory_offset_in_range_p (op, mode, 0, 0); +} + +/* Return 1 if OP is a valid operand for a `short' memory reference insn. */ + +int +short_memory_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + if (mode == VOIDmode) + mode = GET_MODE (op); + + return memory_offset_in_range_p (op, mode, 0, + 15 * min (UNITS_PER_WORD, + GET_MODE_SIZE (mode))); +} + +/* Returns 1 if OP is a memory reference involving a symbolic constant + that is not in the constant pool. */ + +int +symbolic_memory_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (! memory_operand (op, mode)) + return 0; + + while (GET_CODE (op) == SUBREG) + op = SUBREG_REG (op); + + if (GET_CODE (op) != MEM) + abort (); + + op = XEXP (op, 0); + if (constant_pool_address_operand (op, VOIDmode)) + return 0; + else + return romp_symbolic_operand (op, Pmode) + || (GET_CODE (op) == PLUS && register_operand (XEXP (op, 0), Pmode) + && romp_symbolic_operand (XEXP (op, 1), Pmode)); +} + + +/* Returns 1 if OP is a constant pool reference to the current function. */ + +int +current_function_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + if (GET_CODE (op) != MEM || GET_CODE (XEXP (op, 0)) != SYMBOL_REF + || ! CONSTANT_POOL_ADDRESS_P (XEXP (op, 0))) + return 0; + + op = get_pool_constant (XEXP (op, 0)); + return (GET_CODE (op) == SYMBOL_REF + && ! strcmp (current_function_name, XSTR (op, 0))); +} + +/* Return non-zero if this function is known to have a null epilogue. */ + +int +null_epilogue () +{ + return (reload_completed + && first_reg_to_save () == 16 + && ! romp_pushes_stack ()); +} + +/* Returns 1 if OP is the address of a location in the constant pool. */ + +int +constant_pool_address_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return ((GET_CODE (op) == SYMBOL_REF && CONSTANT_POOL_ADDRESS_P (op)) + || (GET_CODE (op) == CONST && GET_CODE (XEXP (op, 0)) == PLUS + && GET_CODE (XEXP (XEXP (op, 0), 1)) == CONST_INT + && GET_CODE (XEXP (XEXP (op, 0), 0)) == SYMBOL_REF + && CONSTANT_POOL_ADDRESS_P (XEXP (XEXP (op, 0), 0)))); +} + +/* Returns 1 if OP is either a symbol reference or a sum of a symbol + reference and a constant. */ + +int +romp_symbolic_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + switch (GET_CODE (op)) + { + case SYMBOL_REF: + case LABEL_REF: + return ! op->integrated; + + case CONST: + op = XEXP (op, 0); + return (GET_CODE (XEXP (op, 0)) == SYMBOL_REF + || GET_CODE (XEXP (op, 0)) == LABEL_REF) + && GET_CODE (XEXP (op, 1)) == CONST_INT; + + default: + return 0; + } +} + +/* Returns 1 if OP is a valid constant for the ROMP. */ + +int +constant_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + switch (GET_CODE (op)) + { + case LABEL_REF: + case SYMBOL_REF: + case PLUS: + case CONST: + return romp_symbolic_operand (op,mode); + + case CONST_INT: + return (unsigned int) (INTVAL (op) + 0x8000) < 0x10000 + || (INTVAL (op) & 0xffff) == 0 || (INTVAL (op) & 0xffff0000) == 0; + + default: + return 0; + } +} + +/* Returns 1 if OP is either a constant integer valid for the ROMP or a + register. If a register, it must be in the proper mode unless MODE is + VOIDmode. */ + +int +reg_or_cint_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (GET_CODE (op) == CONST_INT) + return constant_operand (op, mode); + + return register_operand (op, mode); +} + +/* Return 1 is the operand is either a register or ANY constant integer. */ + +int +reg_or_any_cint_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + return GET_CODE (op) == CONST_INT || register_operand (op, mode); +} + +/* Return 1 if the operand is either a register or a valid D-type operand. */ + +int +reg_or_D_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (GET_CODE (op) == CONST_INT) + return (unsigned) (INTVAL (op) + 0x8000) < 0x10000; + + return register_operand (op, mode); +} + +/* Return 1 if the operand is either a register or an item that can be + used as the operand of an SI add insn. */ + +int +reg_or_add_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + return reg_or_D_operand (op, mode) || romp_symbolic_operand (op, mode) + || (GET_CODE (op) == CONST_INT && (INTVAL (op) & 0xffff) == 0); +} + +/* Return 1 if the operand is either a register or an item that can be + used as the operand of a ROMP logical AND insn. */ + +int +reg_or_and_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (reg_or_cint_operand (op, mode)) + return 1; + + if (GET_CODE (op) != CONST_INT) + return 0; + + return (INTVAL (op) & 0xffff) == 0xffff + || (INTVAL (op) & 0xffff0000) == 0xffff0000; +} + +/* Return 1 if the operand is a register or memory operand. */ + +int +reg_or_mem_operand (op, mode) + register rtx op; + register enum machine_mode mode; +{ + return register_operand (op, mode) || memory_operand (op, mode); +} + +/* Return 1 if the operand is either a register or a memory operand that is + not symbolic. */ + +int +reg_or_nonsymb_mem_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (register_operand (op, mode)) + return 1; + + if (memory_operand (op, mode) && ! symbolic_memory_operand (op, mode)) + return 1; + + return 0; +} + +/* Return 1 if this operand is valid for the ROMP. This is any operand except + certain constant integers. */ + +int +romp_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (GET_CODE (op) == CONST_INT) + return constant_operand (op, mode); + + return general_operand (op, mode); +} + +/* Return 1 if the operand is (reg:mode 0). */ + +int +reg_0_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return ((mode == VOIDmode || mode == GET_MODE (op)) + && GET_CODE (op) == REG && REGNO (op) == 0); +} + +/* Return 1 if the operand is (reg:mode 15). */ + +int +reg_15_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return ((mode == VOIDmode || mode == GET_MODE (op)) + && GET_CODE (op) == REG && REGNO (op) == 15); +} + +/* Return 1 if this is a binary floating-point operation. */ + +int +float_binary (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (mode != VOIDmode && mode != GET_MODE (op)) + return 0; + + if (GET_MODE (op) != SFmode && GET_MODE (op) != DFmode) + return 0; + + switch (GET_CODE (op)) + { + case PLUS: + case MINUS: + case MULT: + case DIV: + return GET_MODE (XEXP (op, 0)) == GET_MODE (op) + && GET_MODE (XEXP (op, 1)) == GET_MODE (op); + + default: + return 0; + } +} + +/* Return 1 if this is a unary floating-point operation. */ + +int +float_unary (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (mode != VOIDmode && mode != GET_MODE (op)) + return 0; + + if (GET_MODE (op) != SFmode && GET_MODE (op) != DFmode) + return 0; + + return (GET_CODE (op) == NEG || GET_CODE (op) == ABS) + && GET_MODE (XEXP (op, 0)) == GET_MODE (op); +} + +/* Return 1 if this is a valid floating-point converstion that can be done + as part of an operation by the RT floating-point routines. */ + +int +float_conversion (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (mode != VOIDmode && mode != GET_MODE (op)) + return 0; + + switch (GET_CODE (op)) + { + case FLOAT_TRUNCATE: + return GET_MODE (op) == SFmode && GET_MODE (XEXP (op, 0)) == DFmode; + + case FLOAT_EXTEND: + return GET_MODE (op) == DFmode && GET_MODE (XEXP (op, 0)) == SFmode; + + case FLOAT: + return ((GET_MODE (XEXP (op, 0)) == SImode + || GET_CODE (XEXP (op, 0)) == CONST_INT) + && (GET_MODE (op) == SFmode || GET_MODE (op) == DFmode)); + + case FIX: + return ((GET_MODE (op) == SImode + || GET_CODE (XEXP (op, 0)) == CONST_INT) + && (GET_MODE (XEXP (op, 0)) == SFmode + || GET_MODE (XEXP (op, 0)) == DFmode)); + + default: + return 0; + } +} + +/* Print an operand. Recognize special options, documented below. */ + +void +print_operand (file, x, code) + FILE *file; + rtx x; + char code; +{ + int i; + + switch (code) + { + case 'B': + /* Byte number (const/8) */ + if (GET_CODE (x) != CONST_INT) + output_operand_lossage ("invalid %%B value"); + + fprintf (file, "%d", INTVAL (x) / 8); + break; + + case 'L': + /* Low order 16 bits of constant. */ + if (GET_CODE (x) != CONST_INT) + output_operand_lossage ("invalid %%L value"); + + fprintf (file, "%d", INTVAL (x) & 0xffff); + break; + + case 's': + /* Null or "16" depending on whether the constant is greater than 16. */ + if (GET_CODE (x) != CONST_INT) + output_operand_lossage ("invalid %%s value"); + + if (INTVAL (x) >= 16) + fprintf (file, "16"); + + break; + + case 'S': + /* For shifts: 's' will have given the half. Just give the amount + within 16. */ + if (GET_CODE (x) != CONST_INT) + output_operand_lossage ("invalid %%S value"); + + fprintf (file, "%d", INTVAL (x) & 15); + break; + + case 'b': + /* The number of a single bit set or cleared, mod 16. Note that the ROMP + numbers bits with the high-order bit 31. */ + if (GET_CODE (x) != CONST_INT) + output_operand_lossage ("invalid %%b value"); + + if ((i = exact_log2 (INTVAL (x))) >= 0) + fprintf (file, "%d", (31 - i) % 16); + else if ((i = exact_log2 (~ INTVAL (x))) >= 0) + fprintf (file, "%d", (31 - i) % 16); + else + output_operand_lossage ("invalid %%b value"); + + break; + + case 'h': + /* "l" or "u" depending on which half of the constant is zero. */ + if (GET_CODE (x) != CONST_INT) + output_operand_lossage ("invalid %%h value"); + + if ((INTVAL (x) & 0xffff0000) == 0) + fprintf (file, "l"); + else if ((INTVAL (x) & 0xffff) == 0) + fprintf (file, "u"); + else + output_operand_lossage ("invalid %%h value"); + + break; + + case 'H': + /* Upper or lower half, depending on which half is zero. */ + if (GET_CODE (x) != CONST_INT) + output_operand_lossage ("invalid %%H value"); + + if ((INTVAL (x) & 0xffff0000) == 0) + fprintf (file, "%d", INTVAL (x) & 0xffff); + else if ((INTVAL (x) & 0xffff) == 0) + fprintf (file, "%d", (INTVAL (x) >> 16) & 0xffff); + else + output_operand_lossage ("invalid %%H value"); + + break; + + case 'z': + /* Write two characters: + 'lo' if the high order part is all ones + 'lz' if the high order part is all zeros + 'uo' if the low order part is all ones + 'uz' if the low order part is all zeros + */ + if (GET_CODE (x) != CONST_INT) + output_operand_lossage ("invalid %%z value"); + + if ((INTVAL (x) & 0xffff0000) == 0) + fprintf (file, "lz"); + else if ((INTVAL (x) & 0xffff0000) == 0xffff0000) + fprintf (file, "lo"); + else if ((INTVAL (x) & 0xffff) == 0) + fprintf (file, "uz"); + else if ((INTVAL (x) & 0xffff) == 0xffff) + fprintf (file, "uo"); + else + output_operand_lossage ("invalid %%z value"); + + break; + + case 'Z': + /* Upper or lower half, depending on which is non-zero or not + all ones. Must be consistent with 'z' above. */ + if (GET_CODE (x) != CONST_INT) + output_operand_lossage ("invalid %%Z value"); + + if ((INTVAL (x) & 0xffff0000) == 0 + || (INTVAL (x) & 0xffff0000) == 0xffff0000) + fprintf (file, "%d", INTVAL (x) & 0xffff); + else if ((INTVAL (x) & 0xffff) == 0 || (INTVAL (x) & 0xffff) == 0xffff) + fprintf (file, "%d", (INTVAL (x) >> 16) & 0xffff); + else + output_operand_lossage ("invalid %%Z value"); + + break; + + case 'k': + /* Same as 'z', except the trailing 'o' or 'z' is not written. */ + if (GET_CODE (x) != CONST_INT) + output_operand_lossage ("invalid %%k value"); + + if ((INTVAL (x) & 0xffff0000) == 0 + || (INTVAL (x) & 0xffff0000) == 0xffff0000) + fprintf (file, "l"); + else if ((INTVAL (x) & 0xffff) == 0 + || (INTVAL (x) & 0xffff) == 0xffff) + fprintf (file, "u"); + else + output_operand_lossage ("invalid %%k value"); + + break; + + case 't': + /* Similar to 's', except that we write 'h' or 'u'. */ + if (GET_CODE (x) != CONST_INT) + output_operand_lossage ("invalid %%k value"); + + if (INTVAL (x) < 16) + fprintf (file, "u"); + else + fprintf (file, "l"); + break; + + case 'M': + /* For memory operations, write 's' if the operand is a short + memory operand. */ + if (short_memory_operand (x, VOIDmode)) + fprintf (file, "s"); + break; + + case 'N': + /* Like 'M', but check for zero memory offset. */ + if (zero_memory_operand (x, VOIDmode)) + fprintf (file, "s"); + break; + + case 'O': + /* Write low-order part of DImode or DFmode. Supported for MEM + and REG only. */ + if (GET_CODE (x) == REG) + fprintf (file, "%s", reg_names[REGNO (x) + 1]); + else if (GET_CODE (x) == MEM) + print_operand (file, gen_rtx (MEM, GET_MODE (x), + plus_constant (XEXP (x, 0), 4)), 0); + else + abort (); + break; + + case 'C': + /* Offset in constant pool for constant pool address. */ + if (! constant_pool_address_operand (x, VOIDmode)) + abort (); + if (GET_CODE (x) == SYMBOL_REF) + fprintf (file, "%d", get_pool_offset (x) + 12); + else + /* Must be (const (plus (symbol_ref) (const_int))) */ + fprintf (file, "%d", + (get_pool_offset (XEXP (XEXP (x, 0), 0)) + 12 + + INTVAL (XEXP (XEXP (x, 0), 1)))); + break; + + case 'j': + /* Branch opcode. Check for condition in test bit for eq/ne. */ + switch (GET_CODE (x)) + { + case EQ: + if (cc_status.flags & CC_IN_TB) + fprintf (file, "ntb"); + else + fprintf (file, "eq"); + break; + + case NE: + if (cc_status.flags & CC_IN_TB) + fprintf (file, "tb"); + else + fprintf (file, "ne"); + break; + + case GT: + case GTU: + fprintf (file, "h"); + break; + + case LT: + case LTU: + fprintf (file, "l"); + break; + + case GE: + case GEU: + fprintf (file, "he"); + break; + + case LE: + case LEU: + fprintf (file, "le"); + break; + + default: + output_operand_lossage ("invalid %%j value"); + } + break; + + case 'J': + /* Reversed branch opcode. */ + switch (GET_CODE (x)) + { + case EQ: + if (cc_status.flags & CC_IN_TB) + fprintf (file, "tb"); + else + fprintf (file, "ne"); + break; + + case NE: + if (cc_status.flags & CC_IN_TB) + fprintf (file, "ntb"); + else + fprintf (file, "eq"); + break; + + case GT: + case GTU: + fprintf (file, "le"); + break; + + case LT: + case LTU: + fprintf (file, "he"); + break; + + case GE: + case GEU: + fprintf (file, "l"); + break; + + case LE: + case LEU: + fprintf (file, "h"); + break; + + default: + output_operand_lossage ("invalid %%j value"); + } + break; + + case '.': + /* Output nothing. Used as delimeter in, e.g., "mc%B1%.3 " */ + break; + + case '#': + /* Output 'x' if this insn has a delay slot, else nothing. */ + if (dbr_sequence_length ()) + fprintf (file, "x"); + break; + + case 0: + if (GET_CODE (x) == REG) + fprintf (file, "%s", reg_names[REGNO (x)]); + else if (GET_CODE (x) == MEM) + { + if (GET_CODE (XEXP (x, 0)) == SYMBOL_REF + && current_function_operand (x, Pmode)) + fprintf (file, "r14"); + else + output_address (XEXP (x, 0)); + } + else + output_addr_const (file, x); + break; + + default: + output_operand_lossage ("invalid %%xn code"); + } +} + +/* This page contains routines that are used to determine what the function + prologue and epilogue code will do and write them out. */ + +/* Return the first register that is required to be saved. 16 if none. */ + +int +first_reg_to_save() +{ + int first_reg; + + /* Find lowest numbered live register. */ + for (first_reg = 6; first_reg <= 15; first_reg++) + if (regs_ever_live[first_reg]) + break; + + /* If we think that we do not have to save r14, see if it will be used + to be sure. */ + if (first_reg > 14 && romp_using_r14 ()) + first_reg = 14; + + return first_reg; +} + +/* Compute the size of the save area in the stack, including the space for + the first four incoming arguments. */ + +int +romp_sa_size () +{ + int size; + int i; + + /* We have the 4 words corresponding to the arguments passed in registers, + 4 reserved words, space for static chain, general register save area, + and floating-point save area. */ + size = 4 + 4 + 1 + (16 - first_reg_to_save ()); + + /* The documentation says we have to leave 18 words in the save area if + any floating-point registers at all are saved, not the three words + per register you might otherwise expect. */ + for (i = 2 + (TARGET_FP_REGS != 0); i <= 7; i++) + if (regs_ever_live[i + 17]) + { + size += 18; + break; + } + + return size * 4; +} + +/* Return non-zero if this function makes calls or has fp operations + (which are really calls). */ + +int +romp_makes_calls () +{ + rtx insn; + + for (insn = get_insns (); insn; insn = next_insn (insn)) + { + if (GET_CODE (insn) == CALL_INSN) + return 1; + else if (GET_CODE (insn) == INSN) + { + rtx body = PATTERN (insn); + + if (GET_CODE (body) != USE && GET_CODE (body) != CLOBBER + && GET_CODE (body) != ADDR_VEC + && GET_CODE (body) != ADDR_DIFF_VEC + && get_attr_type (insn) == TYPE_FP) + return 1; + } + } + + return 0; +} + +/* Return non-zero if this function will use r14 as a pointer to its + constant pool. */ + +int +romp_using_r14 () +{ + /* If we are debugging, profiling, have a non-empty constant pool, or + call a function, we need r14. */ + return (write_symbols != NO_DEBUG || profile_flag || get_pool_size () != 0 + || romp_makes_calls ()); +} + +/* Return non-zero if this function needs to push space on the stack. */ + +int +romp_pushes_stack () +{ + /* We need to push the stack if a frame pointer is needed (because the + stack might be dynamically adjusted), if we are debugging, if the + total required size is more than 100 bytes, or if we make calls. */ + + return (frame_pointer_needed || write_symbols != NO_DEBUG + || (romp_sa_size () + get_frame_size ()) > 100 + || romp_makes_calls ()); +} + +/* Write function prologue. + + We compute the size of the fixed area required as follows: + + We always allocate 4 words for incoming arguments, 4 word reserved, 1 + word for static link, as many words as required for general register + save area, plus 2 words for each FP reg 2-7 that must be saved. */ + +void +output_prolog (file, size) + FILE *file; + int size; +{ + int first_reg; + int reg_save_offset; + rtx insn; + int fp_save = size + current_function_outgoing_args_size; + + init_fpops (); + + /* Add in fixed size plus output argument area. */ + size += romp_sa_size () + current_function_outgoing_args_size; + + /* Compute first register to save and perform the save operation if anything + needs to be saved. */ + first_reg = first_reg_to_save(); + reg_save_offset = - (4 + 4 + 1 + (16 - first_reg)) * 4; + if (first_reg == 15) + fprintf (file, "\tst r15,%d(r1)\n", reg_save_offset); + else if (first_reg < 16) + fprintf (file, "\tstm r%d,%d(r1)\n", first_reg, reg_save_offset); + + /* Set up pointer to data area if it is needed. */ + if (romp_using_r14 ()) + fprintf (file, "\tcas r14,r0,r0\n"); + + /* Set up frame pointer if needed. */ + if (frame_pointer_needed) + fprintf (file, "\tcal r13,-%d(r1)\n", romp_sa_size () + 64); + + /* Push stack if neeeded. There are a couple of ways of doing this. */ + if (romp_pushes_stack ()) + { + if (size >= 32768) + { + if (size >= 65536) + { + fprintf (file, "\tcau r0,%d(r0)\n", size >> 16); + fprintf (file, "\toil r0,r0,%d\n", size & 0xffff); + } + else + fprintf (file, "\tcal16 r0,%d(r0)\n", size); + fprintf (file, "\ts r1,r0\n"); + } + else + fprintf (file, "\tcal r1,-%d(r1)\n", size); + } + + /* Save floating-point registers. */ + output_loadsave_fpregs (file, USE, + plus_constant (stack_pointer_rtx, fp_save)); +} + +/* Write function epilogue. */ + +void +output_epilog (file, size) + FILE *file; + int size; +{ + int first_reg = first_reg_to_save(); + int pushes_stack = romp_pushes_stack (); + int reg_save_offset = - ((16 - first_reg) + 1 + 4 + 4) * 4; + int total_size = (size + romp_sa_size () + + current_function_outgoing_args_size); + int fp_save = size + current_function_outgoing_args_size; + int long_frame = total_size >= 32768; + rtx insn = get_last_insn (); + int write_code = 1; + + int nargs = 0; /* words of arguments */ + tree argptr; + + for (argptr = DECL_ARGUMENTS (current_function_decl); + argptr; argptr = TREE_CHAIN (argptr)) + nargs += ((TREE_INT_CST_LOW (TYPE_SIZE (TREE_TYPE (argptr))) + + BITS_PER_WORD - 1) / BITS_PER_WORD); + + /* If the last insn was a BARRIER, we don't have to write anything except + the trace table. */ + if (GET_CODE (insn) == NOTE) + insn = prev_nonnote_insn (insn); + if (insn && GET_CODE (insn) == BARRIER) + write_code = 0; + + /* Restore floating-point registers. */ + if (write_code) + output_loadsave_fpregs (file, CLOBBER, + gen_rtx (PLUS, Pmode, gen_rtx (REG, Pmode, 1), + gen_rtx (CONST_INT, VOIDmode, fp_save))); + + /* If we push the stack and do not have size > 32K, adjust the register + save location to the current position of sp. Otherwise, if long frame, + restore sp from fp. */ + if (pushes_stack && ! long_frame) + reg_save_offset += total_size; + else if (long_frame && write_code) + fprintf (file, "\tcal r1,%d(r13)\n", romp_sa_size () + 64); + + /* Restore registers. */ + if (first_reg == 15 && write_code) + fprintf (file, "\tl r15,%d(r1)\n", reg_save_offset); + else if (first_reg < 16 && write_code) + fprintf (file, "\tlm r%d,%d(r1)\n", first_reg, reg_save_offset); + if (first_reg == 16) first_reg = 0; + + /* Handle popping stack, if needed and write debug table entry. */ + if (pushes_stack) + { + if (write_code) + { + if (long_frame) + fprintf (file, "\tbr r15\n"); + else + fprintf (file, "\tbrx r15\n\tcal r1,%d(r1)\n", total_size); + } + fprintf (file, "\t.long 0x%x\n", 0xdf07df08 + first_reg * 0x10); + + if (nargs > 15) nargs = 15; + if (frame_pointer_needed) + fprintf (file, "\t.byte 0x%xd, 53\n", nargs); + else + fprintf (file, "\t.short 0x%x100\n", nargs); + } + else + { + if (write_code) + fprintf (file, "\tbr r15\n"); + fprintf (file, "\t.long 0xdf02df00\n"); + } + + /* Output any pending floating-point operations. */ + if (write_code) + output_fpops (file); +} + +/* For the ROMP we need to make new SYMBOL_REFs for the actual name of a + called routine. To keep them unique we maintain a hash table of all + that have been created so far. */ + +struct symref_hashent { + rtx symref; /* Created SYMBOL_REF rtx. */ + struct symref_hashent *next; /* Next with same hash code. */ +}; + +#define SYMHASHSIZE 151 +#define HASHBITS 65535 + +/* Define the hash table itself. */ + +static struct symref_hashent *symref_hash_table[SYMHASHSIZE]; + +/* Given a name (allocatable in temporary storage), return a SYMBOL_REF + for the name. The rtx is allocated from the current rtl_obstack, while + the name string is allocated from the permanent obstack. */ +rtx +get_symref (name) + register char *name; +{ + extern struct obstack permanent_obstack; + register char *sp = name; + unsigned int hash = 0; + struct symref_hashent *p, **last_p; + + /* Compute the hash code for the string. */ + while (*sp) + hash = (hash << 4) + *sp++; + + /* Search for a matching entry in the hash table, keeping track of the + insertion location as we do so. */ + hash = (hash & HASHBITS) % SYMHASHSIZE; + for (last_p = &symref_hash_table[hash], p = *last_p; + p; last_p = &p->next, p = *last_p) + if (strcmp (name, XSTR (p->symref, 0)) == 0) + break; + + /* If couldn't find matching SYMBOL_REF, make a new one. */ + if (p == 0) + { + /* Ensure SYMBOL_REF will stay around. */ + end_temporary_allocation (); + p = *last_p = (struct symref_hashent *) + permalloc (sizeof (struct symref_hashent)); + p->symref = gen_rtx (SYMBOL_REF, Pmode, + obstack_copy0 (&permanent_obstack, + name, strlen (name))); + p->next = 0; + resume_temporary_allocation (); + } + + return p->symref; +} + +/* Validate the precision of a floating-point operation. + + We merge conversions from integers and between floating-point modes into + the insn. However, this must not effect the desired precision of the + insn. The RT floating-point system uses the widest of the operand modes. + If this should be a double-precision insn, ensure that one operand + passed to the floating-point processor has double mode. + + Note that since we don't check anything if the mode is single precision, + it, strictly speaking, isn't necessary to call this for those insns. + However, we do so in case something else needs to be checked in the + future. + + This routine returns 1 if the operation is OK. */ + +int +check_precision (opmode, op1, op2) + enum machine_mode opmode; + rtx op1, op2; +{ + if (opmode == SFmode) + return 1; + + /* If operand is not a conversion from an integer mode or an extension from + single-precision, it must be a double-precision value. */ + if (GET_CODE (op1) != FLOAT && GET_CODE (op1) != FLOAT_EXTEND) + return 1; + + if (op2 && GET_CODE (op2) != FLOAT && GET_CODE (op2) != FLOAT_EXTEND) + return 1; + + return 0; +} + +/* Floating-point on the RT is done by creating an operation block in the data + area that describes the operation. If two floating-point operations are the + same in a single function, they can use the same block. + + These routines are responsible for managing these blocks. */ + +/* Structure to describe a floating-point operation. */ + +struct fp_op { + struct fp_op *next_same_hash; /* Next op with same hash code. */ + struct fp_op *next_in_mem; /* Next op in memory. */ + int mem_offset; /* Offset from data area. */ + short size; /* Size of block in bytes. */ + short noperands; /* Number of operands in block. */ + rtx ops[3]; /* RTL for operands. */ + enum rtx_code opcode; /* Operation being performed. */ +}; + +/* Size of hash table. */ +#define FP_HASH_SIZE 101 + +/* Hash table of floating-point operation blocks. */ +static struct fp_op *fp_hash_table[FP_HASH_SIZE]; + +/* First floating-point block in data area. */ +static struct fp_op *first_fpop; + +/* Last block in data area so far. */ +static struct fp_op *last_fpop_in_mem; + +/* Subroutine number in file, to get unique "LF" labels. */ +static int subr_number = 0; + +/* Current word offset in data area (includes header and any constant pool). */ +int data_offset; + +/* Compute hash code for an RTX used in floating-point. */ + +static unsigned int +hash_rtx (x) + register rtx x; +{ + register unsigned int hash = (((int) GET_CODE (x) << 10) + + ((int) GET_MODE (x) << 20)); + register int i; + register char *fmt = GET_RTX_FORMAT (GET_CODE (x)); + + for (i = 0; i < GET_RTX_LENGTH (GET_CODE (x)); i++) + if (fmt[i] == 'e') + hash += hash_rtx (XEXP (x, i)); + else if (fmt[i] == 'u') + hash += (int) XEXP (x, i); + else if (fmt[i] == 'i') + hash += XINT (x, i); + else if (fmt[i] == 's') + hash += (int) XSTR (x, i); + + return hash; +} + +/* Given an operation code and up to three operands, return a character string + corresponding to the code to emit to branch to a floating-point operation + block. INSN is provided to see if the delay slot has been filled or not. + + A new floating-point operation block is created if this operation has not + been seen before. */ + +char * +output_fpop (code, op0, op1, op2, insn) + enum rtx_code code; + rtx op0, op1, op2; + rtx insn; +{ + static char outbuf[40]; + unsigned int hash, hash0, hash1, hash2; + int size, i; + register struct fp_op *fpop, *last_fpop; + int dyadic = (op2 != 0); + enum machine_mode opmode; + int noperands; + rtx tem; + unsigned int tem_hash; + int fr0_avail = 0; + + /* Compute hash code for each operand. If the operation is commutative, + put the one with the smaller hash code first. This will make us see + more operations as identical. */ + hash0 = op0 ? hash_rtx (op0) : 0; + hash1 = op1 ? hash_rtx (op1) : 0; + hash2 = op2 ? hash_rtx (op2) : 0; + + if (hash0 > hash1 && code == EQ) + { + tem = op0; op0 = op1; op1 = tem; + tem_hash = hash0; hash0 = hash1; hash1 = tem_hash; + } + else if (hash1 > hash2 && (code == PLUS || code == MULT)) + { + tem = op1; op1 = op2; op2 = tem; + tem_hash = hash1; hash1 = hash2; hash2 = tem_hash; + } + + /* If operation is commutative and the first and third operands are equal, + swap the second and third operands. Note that we must consider two + operands equal if they are the same register even if different modes. */ + if (op2 && (code == PLUS || code == MULT) + && (rtx_equal_p (op0, op2) + || (GET_CODE (op0) == REG && GET_CODE (op2) == REG + && REGNO (op0) == REGNO (op2)))) + { + tem = op1; op1 = op2; op2 = tem; + tem_hash = hash1; hash1 = hash2; hash2 = tem_hash; + } + + /* If the first and second operands are the same, merge them. Don't do this + for SFmode in general registers because this triggers a bug in the RT fp + code. */ + if (op1 && rtx_equal_p (op0, op1) + && code != EQ && code != GE && code != SET + && (GET_MODE (op1) != SFmode || GET_CODE (op0) != REG + || FP_REGNO_P (REGNO (op0)))) + { + op1 = op2; + op2 = 0; + } + + noperands = 1 + (op1 != 0) + (op2 != 0); + + /* Compute hash code for entire expression and see if operation block + already exists. */ + hash = ((int) code << 13) + (hash0 << 2) + (hash1 << 1) + hash2; + + hash %= FP_HASH_SIZE; + for (fpop = fp_hash_table[hash], last_fpop = 0; + fpop; + last_fpop = fpop, fpop = fpop->next_same_hash) + if (fpop->opcode == code && noperands == fpop->noperands + && (op0 == 0 || rtx_equal_p (op0, fpop->ops[0])) + && (op1 == 0 || rtx_equal_p (op1, fpop->ops[1])) + && (op2 == 0 || rtx_equal_p (op2, fpop->ops[2]))) + goto win; + + /* We have never seen this operation before. */ + fpop = (struct fp_op *) oballoc (sizeof (struct fp_op)); + fpop->mem_offset = data_offset; + fpop->opcode = code; + fpop->noperands = noperands; + fpop->ops[0] = op0; + fpop->ops[1] = op1; + fpop->ops[2] = op2; + + /* Compute the size using the rules in Appendix A of the RT Linkage + Convention (4.3/RT-PSD:5) manual. These rules are a bit ambiguous, + but if we guess wrong, it will effect only efficiency, not correctness. */ + + /* Size = 24 + 32 for each non-fp (or fr7) */ + size = 24; + if (op0 && (GET_CODE (op0) != REG + || ! FP_REGNO_P (REGNO (op0)) || REGNO (op0) == 23)) + size += 32; + + if (op1 && (GET_CODE (op1) != REG + || ! FP_REGNO_P (REGNO (op1)) || REGNO (op1) == 23)) + size += 32; + + if (op2 && (GET_CODE (op2) != REG + || ! FP_REGNO_P (REGNO (op2)) || REGNO (op2) == 23)) + size += 32; + + /* Size + 12 for each conversion. First get operation mode. */ + if ((op0 && GET_MODE (op0) == DFmode) + || (op1 && GET_MODE (op1) == DFmode) + || (op2 && GET_MODE (op2) == DFmode)) + opmode = DFmode; + else + opmode = SFmode; + + if (op0 && GET_MODE (op0) != opmode) + size += 12; + if (op1 && GET_MODE (op1) != opmode) + size += 12; + if (op2 && GET_MODE (op2) != opmode) + size += 12; + + /* 12 more if first and third operand types not the same. */ + if (op2 && GET_MODE (op0) != GET_MODE (op2)) + size += 12; + + /* CMP and CMPT need additional. Also, compute size of save/restore here. */ + if (code == EQ) + size += 32; + else if (code == GE) + size += 64; + else if (code == USE || code == CLOBBER) + { + /* 34 + 24 for each additional register plus 8 if fr7 saved. (We + call it 36 because we need to keep the block length a multiple + of four. */ + size = 36 - 24; + for (i = 0; i <= 7; i++) + if (INTVAL (op0) & (1 << (7-i))) + size += 24 + 8 * (i == 7); + } + + /* We provide no general-purpose scratch registers. */ + size +=16; + + /* No floating-point scratch registers are provided. Compute extra + length due to this. This logic is that shown in the referenced + appendix. */ + + i = 0; + if (op0 && GET_CODE (op0) == REG && FP_REGNO_P (REGNO (op0))) + i++; + if (op1 && GET_CODE (op1) == REG && FP_REGNO_P (REGNO (op1))) + i++; + if (op2 && GET_CODE (op2) == REG && FP_REGNO_P (REGNO (op2))) + i++; + + if ((op0 == 0 || GET_CODE (op0) != REG || REGNO(op0) != 17) + && (op1 == 0 || GET_CODE (op1) != REG || REGNO(op1) != 17) + && (op2 == 0 || GET_CODE (op2) != REG || REGNO(op2) != 17)) + fr0_avail = 1; + + if (dyadic) + { + if (i == 0) + size += fr0_avail ? 64 : 112; + else if (fpop->noperands == 2 && i == 1) + size += fr0_avail ? 0 : 64; + else if (fpop->noperands == 3) + { + if (GET_CODE (op0) == REG && FP_REGNO_P (REGNO (op0)) + && GET_CODE (op2) == REG && FP_REGNO_P (REGNO (op2))) + { + if (REGNO (op0) == REGNO (op2)) +#if 1 + /* This triggers a bug on the RT. */ + abort (); +#else + size += fr0_avail ? 0 : 64; +#endif + } + else + { + i = 0; + if (GET_CODE (op0) == REG && FP_REGNO_P (REGNO (op0))) + i++; + if (GET_CODE (op2) == REG && FP_REGNO_P (REGNO (op2))) + i++; + if (i == 0) + size += fr0_avail ? 64 : 112; + else if (i == 1) + size += fr0_avail ? 0 : 64; + } + } + } + else if (code != USE && code != CLOBBER + && (GET_CODE (op0) != REG || ! FP_REGNO_P (REGNO (op0)))) + size += 64; + + if (! TARGET_FULL_FP_BLOCKS) + { + /* If we are not to pad the blocks, just compute its actual length. */ + size = 12; /* Header + opcode */ + if (code == USE || code == CLOBBER) + size += 2; + else + { + if (op0) size += 2; + if (op1) size += 2; + if (op2) size += 2; + } + + /* If in the middle of a word, round. */ + if (size % UNITS_PER_WORD) + size += 2; + + /* Handle any immediates. */ + if (code != USE && code != CLOBBER && op0 && GET_CODE (op0) != REG) + size += 4; + if (op1 && GET_CODE (op1) != REG) + size += 4; + if (op2 && GET_CODE (op2) != REG) + size += 4; + + if (code != USE && code != CLOBBER && + op0 && GET_CODE (op0) == CONST_DOUBLE && GET_MODE (op0) == DFmode) + size += 4; + if (op1 && GET_CODE (op1) == CONST_DOUBLE && GET_MODE (op1) == DFmode) + size += 4; + if (op2 && GET_CODE (op2) == CONST_DOUBLE && GET_MODE (op2) == DFmode) + size += 4; + } + + /* Done with size computation! Chain this in. */ + fpop->size = size; + data_offset += size / UNITS_PER_WORD; + fpop->next_in_mem = 0; + fpop->next_same_hash = 0; + + if (last_fpop_in_mem) + last_fpop_in_mem->next_in_mem = fpop; + else + first_fpop = fpop; + last_fpop_in_mem = fpop; + + if (last_fpop) + last_fpop->next_same_hash = fpop; + else + fp_hash_table[hash] = fpop; + +win: + /* FPOP describes the operation to be performed. Return a string to branch + to it. */ + if (fpop->mem_offset < 32768 / UNITS_PER_WORD) + sprintf (outbuf, "cal r15,%d(r14)\n\tbalr%s r15,r15", + fpop->mem_offset * UNITS_PER_WORD, + dbr_sequence_length () ? "x" : ""); + else + sprintf (outbuf, "get r15,$L%dF%d\n\tbalr%s r15,r15", + subr_number, fpop->mem_offset * UNITS_PER_WORD, + dbr_sequence_length () ? "x" : ""); + return outbuf; +} + +/* If necessary, output a floating-point operation to save or restore all + floating-point registers. + + file is the file to write the operation to, CODE is USE for save, CLOBBER + for restore, and ADDR is the address of the same area, as RTL. */ + +static void +output_loadsave_fpregs (file, code, addr) + FILE *file; + enum rtx_code code; + rtx addr; +{ + register int i; + register int mask = 0; + + for (i = 2 + (TARGET_FP_REGS != 0); i <= 7; i++) + if (regs_ever_live[i + 17]) + mask |= 1 << (7 - i); + + if (mask) + fprintf (file, "\t%s\n", + output_fpop (code, gen_rtx (CONST_INT, VOIDmode, mask), + gen_rtx (MEM, Pmode, addr), + 0, const0_rtx)); + +} + +/* Output any floating-point operations at the end of the routine. */ + +static void +output_fpops (file) + FILE *file; +{ + register struct fp_op *fpop; + register int size_so_far; + register int i; + rtx immed[3]; + + if (first_fpop == 0) + return; + + data_section (); + + ASM_OUTPUT_ALIGN (file, 2); + + for (fpop = first_fpop; fpop; fpop = fpop->next_in_mem) + { + if (fpop->mem_offset < 32768 / UNITS_PER_WORD) + fprintf (file, "# data area offset = %d\n", + fpop->mem_offset * UNITS_PER_WORD); + else + fprintf (file, "L%dF%d:\n", + subr_number, fpop->mem_offset * UNITS_PER_WORD); + + fprintf (file, "\tcas r0,r15,r0\n"); + fprintf (file, "\t.long FPGLUE\n"); + switch (fpop->opcode) + { + case USE: + fprintf (file, "\t.byte 0x1d\t# STOREM\n"); + break; + case CLOBBER: + fprintf (file, "\t.byte 0x0f\t# LOADM\n"); + break; + case ABS: + fprintf (file, "\t.byte 0x00\t# ABS\n"); + break; + case PLUS: + fprintf (file, "\t.byte 0x02\t# ADD\n"); + break; + case EQ: + fprintf (file, "\t.byte 0x07\t# CMP\n"); + break; + case GE: + fprintf (file, "\t.byte 0x08\t# CMPT\n"); + break; + case DIV: + fprintf (file, "\t.byte 0x0c\t# DIV\n"); + break; + case SET: + fprintf (file, "\t.byte 0x14\t# MOVE\n"); + break; + case MULT: + fprintf (file, "\t.byte 0x15\t# MUL\n"); + break; + case NEG: + fprintf (file, "\t.byte 0x16\t# NEG\n"); + break; + case SQRT: + fprintf (file, "\t.byte 0x1c\t# SQRT\n"); + break; + case MINUS: + fprintf (file, "\t.byte 0x1e\t# SUB\n"); + break; + default: + abort (); + } + + fprintf (file, "\t.byte %d\n", fpop->noperands); + fprintf (file, "\t.short 0x8001\n"); + + if ((fpop->ops[0] == 0 + || GET_CODE (fpop->ops[0]) != REG || REGNO(fpop->ops[0]) != 17) + && (fpop->ops[1] == 0 || GET_CODE (fpop->ops[1]) != REG + || REGNO(fpop->ops[1]) != 17) + && (fpop->ops[2] == 0 || GET_CODE (fpop->ops[2]) != REG + || REGNO(fpop->ops[2]) != 17)) + fprintf (file, "\t.byte %d, 0x80\n", fpop->size); + else + fprintf (file, "\t.byte %d, 0\n", fpop->size); + size_so_far = 12; + for (i = 0; i < fpop->noperands; i++) + { + register int type; + register int opbyte; + register char *desc0; + char desc1[50]; + + immed[i] = 0; + switch (GET_MODE (fpop->ops[i])) + { + case SImode: + case VOIDmode: + desc0 = "int"; + type = 0; + break; + case SFmode: + desc0 = "float"; + type = 2; + break; + case DFmode: + desc0 = "double"; + type = 3; + break; + default: + abort (); + } + + switch (GET_CODE (fpop->ops[i])) + { + case REG: + strcpy(desc1, reg_names[REGNO (fpop->ops[i])]); + if (FP_REGNO_P (REGNO (fpop->ops[i]))) + { + type += 0x10; + opbyte = REGNO (fpop->ops[i]) - 17; + } + else + { + type += 0x00; + opbyte = REGNO (fpop->ops[i]); + if (type == 3) + opbyte = (opbyte << 4) + opbyte + 1; + } + break; + + case MEM: + type += 0x30; + if (GET_CODE (XEXP (fpop->ops[i], 0)) == PLUS) + { + immed[i] = XEXP (XEXP (fpop->ops[i], 0), 1); + opbyte = REGNO (XEXP (XEXP (fpop->ops[i], 0), 0)); + if (GET_CODE (immed[i]) == CONST_INT) + sprintf (desc1, "%d(%s)", INTVAL (immed[i]), + reg_names[opbyte]); + else + sprintf (desc1, "<memory> (%s)", reg_names[opbyte]); + } + else if (GET_CODE (XEXP (fpop->ops[i], 0)) == REG) + { + opbyte = REGNO (XEXP (fpop->ops[i], 0)); + immed[i] = const0_rtx; + sprintf (desc1, "(%s)", reg_names[opbyte]); + } + else + { + immed[i] = XEXP (fpop->ops[i], 0); + opbyte = 0; + sprintf(desc1, "<memory>"); + } + break; + + case CONST_INT: + case CONST_DOUBLE: + case CONST: + type += 0x20; + opbyte = 0; + immed[i] = fpop->ops[i]; + desc1[0] = '$'; + desc1[1] = '\0'; + break; + + default: + abort (); + } + + /* Save/restore is special. */ + if (i == 0 && (fpop->opcode == USE || fpop->opcode == CLOBBER)) + type = 0xff, opbyte = INTVAL (fpop->ops[0]), immed[i] = 0; + + fprintf (file, "\t.byte 0x%x,0x%x # (%s) %s\n", + type, opbyte, desc0, desc1); + + size_so_far += 2; + } + + /* If in the middle of a word, round. */ + if (size_so_far % UNITS_PER_WORD) + { + fprintf (file, "\t.space 2\n"); + size_so_far += 2; + } + + for (i = 0; i < fpop->noperands; i++) + if (immed[i]) + switch (GET_MODE (immed[i])) + { + case SImode: + case VOIDmode: + size_so_far += 4; + fprintf (file, "\t.long "); + output_addr_const (file, immed[i]); + fprintf (file, "\n"); + break; + + case DFmode: + size_so_far += 4; + case SFmode: + size_so_far += 4; + if (GET_CODE (immed[i]) == CONST_DOUBLE) + { + union real_extract u; + + bcopy (&CONST_DOUBLE_LOW (immed[i]), &u, sizeof u); + if (GET_MODE (immed[i]) == DFmode) + ASM_OUTPUT_DOUBLE (file, u.d); + else + ASM_OUTPUT_FLOAT (file, u.d); + } + else + abort (); + break; + + default: + abort (); + } + + if (size_so_far != fpop->size) + { + if (TARGET_FULL_FP_BLOCKS) + fprintf (file, "\t.space %d\n", fpop->size - size_so_far); + else + abort (); + } + } + + /* Update for next subroutine. */ + subr_number++; + text_section (); +} + + /* Initialize floating-point operation table. */ + +static void +init_fpops() +{ + register int i; + + first_fpop = last_fpop_in_mem = 0; + for (i = 0; i < FP_HASH_SIZE; i++) + fp_hash_table[i] = 0; +} |