| /* Subroutines used for code generation on AMD Am29000. |
| Copyright (C) 1987, 88, 90-94, 1995, 1997 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, 59 Temple Place - Suite 330, |
| Boston, MA 02111-1307, USA. */ |
| |
| #include "config.h" |
| #include <stdio.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" |
| #include "reload.h" |
| |
| #define min(A,B) ((A) < (B) ? (A) : (B)) |
| |
| /* This gives the size in words of the register stack for the current |
| procedure. */ |
| |
| static int a29k_regstack_size; |
| |
| /* True if the current procedure has a call instruction. */ |
| |
| static int a29k_makes_calls; |
| |
| /* This points to the last insn of the insn prologue. It is set when |
| an insn without a filled delay slot is found near the start of the |
| function. */ |
| |
| static char *a29k_last_prologue_insn; |
| |
| /* This points to the first insn that will be in the epilogue. It is null if |
| no epilogue is required. */ |
| |
| static char *a29k_first_epilogue_insn; |
| |
| /* This is nonzero if a a29k_first_epilogue_insn was put in a delay slot. It |
| indicates that an intermediate label needs to be written. */ |
| |
| static int a29k_first_epilogue_insn_used; |
| |
| /* Location to hold the name of the current function. We need this prolog to |
| contain the tag words prior to the declaration. So the name must be stored |
| away. */ |
| |
| char *a29k_function_name; |
| |
| /* Mapping of registers to debug register numbers. The only change is |
| for the frame pointer and the register numbers used for the incoming |
| arguments. */ |
| |
| int a29k_debug_reg_map[FIRST_PSEUDO_REGISTER]; |
| |
| /* Save information from a "cmpxx" operation until the branch or scc is |
| emitted. */ |
| |
| rtx a29k_compare_op0, a29k_compare_op1; |
| int a29k_compare_fp_p; |
| |
| /* Gives names for registers. */ |
| extern char *reg_names[]; |
| |
| /* Returns 1 if OP is a 8-bit constant. */ |
| |
| int |
| cint_8_operand (op, mode) |
| register rtx op; |
| enum machine_mode mode; |
| { |
| return GET_CODE (op) == CONST_INT && (INTVAL (op) & 0xffffff00) == 0; |
| } |
| |
| /* Returns 1 if OP is a 16-bit constant. */ |
| |
| int |
| cint_16_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| return GET_CODE (op) == CONST_INT && (INTVAL (op) & 0xffff0000) == 0; |
| } |
| |
| /* Returns 1 if OP is a constant that cannot be moved in a single insn. */ |
| |
| int |
| long_const_operand (op, mode) |
| register rtx op; |
| enum machine_mode mode; |
| { |
| if (! CONSTANT_P (op)) |
| return 0; |
| |
| if (TARGET_29050 && GET_CODE (op) == CONST_INT |
| && (INTVAL (op) & 0xffff) == 0) |
| return 0; |
| |
| return (GET_CODE (op) != CONST_INT |
| || ((INTVAL (op) & 0xffff0000) != 0 |
| && (INTVAL (op) & 0xffff0000) != 0xffff0000 |
| && INTVAL (op) != 0x80000000)); |
| } |
| |
| /* The following four functions detect constants of 0, 8, 16, and 24 used as |
| a position in ZERO_EXTRACT operations. They can either be the appropriate |
| constant integer or a shift (which will be produced by combine). */ |
| |
| static int |
| shift_constant_operand (op, mode, val) |
| rtx op; |
| enum machine_mode mode; |
| int val; |
| { |
| return ((GET_CODE (op) == CONST_INT && INTVAL (op) == val) |
| || (GET_CODE (op) == ASHIFT |
| && GET_CODE (XEXP (op, 0)) == CONST_INT |
| && INTVAL (XEXP (op, 0)) == val / 8 |
| && GET_CODE (XEXP (op, 1)) == CONST_INT |
| && INTVAL (XEXP (op, 1)) == 3)); |
| } |
| |
| int |
| const_0_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| return shift_constant_operand (op, mode, 0); |
| } |
| |
| int |
| const_8_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| return shift_constant_operand (op, mode, 8); |
| } |
| |
| int |
| const_16_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| return shift_constant_operand (op, mode, 16); |
| } |
| |
| int |
| const_24_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| return shift_constant_operand (op, mode, 24); |
| } |
| |
| /* Returns 1 if OP is a floating-point constant of the proper mode. */ |
| |
| int |
| float_const_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| return GET_CODE (op) == CONST_DOUBLE && GET_MODE (op) == mode; |
| } |
| |
| /* Returns 1 if OP is a floating-point constant of the proper mode or a |
| general-purpose register. */ |
| |
| int |
| gpc_reg_or_float_constant_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| return float_const_operand (op, mode) || gpc_reg_operand (op, mode); |
| } |
| |
| /* Returns 1 if OP is an integer constant of the proper mode or a |
| general-purpose register. */ |
| |
| int |
| gpc_reg_or_integer_constant_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| return ((GET_MODE (op) == VOIDmode |
| && (GET_CODE (op) == CONST_INT || GET_CODE (op) == CONST_DOUBLE)) |
| || gpc_reg_operand (op, mode)); |
| } |
| |
| /* Returns 1 if OP is a special machine register. */ |
| |
| int |
| spec_reg_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| if (GET_CODE (op) != REG || GET_MODE (op) != mode) |
| return 0; |
| |
| switch (GET_MODE_CLASS (mode)) |
| { |
| case MODE_PARTIAL_INT: |
| return REGNO (op) >= R_BP && REGNO (op) <= R_CR; |
| case MODE_INT: |
| return REGNO (op) >= R_Q && REGNO (op) <= R_EXO; |
| default: |
| return 0; |
| } |
| } |
| |
| /* Returns 1 if OP is an accumulator register. */ |
| |
| int |
| accum_reg_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| return (GET_CODE (op) == REG |
| && REGNO (op) >= R_ACU (0) && REGNO (op) <= R_ACU (3)); |
| } |
| |
| /* Returns 1 if OP is a normal data register. */ |
| |
| int |
| gpc_reg_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| int regno; |
| |
| if (GET_MODE (op) != mode && mode != VOIDmode) |
| return 0; |
| |
| if (GET_CODE (op) == REG) |
| regno = REGNO (op); |
| else if (GET_CODE (op) == SUBREG && GET_CODE (SUBREG_REG (op)) == REG) |
| { |
| regno = REGNO (SUBREG_REG (op)); |
| if (regno < FIRST_PSEUDO_REGISTER) |
| regno += SUBREG_WORD (op); |
| } |
| else |
| return 0; |
| |
| return (regno >= FIRST_PSEUDO_REGISTER || regno < R_BP |
| || (regno >= R_KR (0) && regno <= R_KR (31))); |
| } |
| |
| /* Returns 1 if OP is either an 8-bit constant integer or a general register. |
| If a register, it must be in the proper mode unless MODE is VOIDmode. */ |
| |
| int |
| srcb_operand (op, mode) |
| register rtx op; |
| enum machine_mode mode; |
| { |
| if (GET_CODE (op) == CONST_INT |
| && (mode == QImode |
| || (INTVAL (op) & 0xffffff00) == 0)) |
| return 1; |
| |
| if (GET_MODE (op) != mode && mode != VOIDmode) |
| return 0; |
| |
| return gpc_reg_operand (op, mode); |
| } |
| |
| int |
| cmplsrcb_operand (op, mode) |
| register rtx op; |
| enum machine_mode mode; |
| { |
| if (GET_CODE (op) == CONST_INT |
| && (mode == QImode |
| || (INTVAL (op) & 0xffffff00) == 0xffffff00)) |
| return 1; |
| |
| if (GET_MODE (op) != mode && mode != VOIDmode) |
| return 0; |
| |
| return gpc_reg_operand (op, mode); |
| } |
| |
| /* Return 1 if OP is either an immediate or a general register. This is used |
| for the input operand of mtsr/mtrsim. */ |
| |
| int |
| gpc_reg_or_immediate_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| return gpc_reg_operand (op, mode) || immediate_operand (op, mode); |
| } |
| |
| /* Return 1 if OP can be used as the second operand of and AND insn. This |
| includes srcb_operand and a constant whose complement fits in 8 bits. */ |
| |
| int |
| and_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| return (srcb_operand (op, mode) |
| || (GET_CODE (op) == CONST_INT |
| && ((unsigned) ((~ INTVAL (op)) & GET_MODE_MASK (mode)) < 256))); |
| } |
| |
| /* Return 1 if OP can be used as the second operand of an ADD insn. |
| This is the same as above, except we use negative, rather than |
| complement. */ |
| |
| int |
| add_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| return (srcb_operand (op, mode) |
| || (GET_CODE (op) == CONST_INT |
| && ((unsigned) ((- INTVAL (op)) & GET_MODE_MASK (mode)) < 256))); |
| } |
| |
| /* Return 1 if OP is a valid address in a CALL_INSN. These are a SYMBOL_REF |
| to the current function, all SYMBOL_REFs if TARGET_SMALL_MEMORY, or |
| a sufficiently-small constant. */ |
| |
| int |
| call_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| switch (GET_CODE (op)) |
| { |
| case SYMBOL_REF: |
| return (TARGET_SMALL_MEMORY |
| || (! TARGET_LARGE_MEMORY |
| && ((GET_CODE (op) == SYMBOL_REF && SYMBOL_REF_FLAG (op)) |
| || ! strcmp (XSTR (op, 0), current_function_name)))); |
| |
| case CONST_INT: |
| return (unsigned HOST_WIDE_INT) INTVAL (op) < 0x40000; |
| |
| default: |
| return 0; |
| } |
| } |
| |
| /* Return 1 if OP can be used as the input operand for a move insn. */ |
| |
| int |
| in_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| rtx orig_op = op; |
| |
| if (! general_operand (op, mode)) |
| return 0; |
| |
| while (GET_CODE (op) == SUBREG) |
| op = SUBREG_REG (op); |
| |
| switch (GET_CODE (op)) |
| { |
| case REG: |
| return 1; |
| |
| case MEM: |
| return (GET_MODE_SIZE (mode) >= UNITS_PER_WORD || TARGET_DW_ENABLE); |
| |
| case CONST_INT: |
| if (GET_MODE_CLASS (mode) != MODE_INT |
| && GET_MODE_CLASS (mode) != MODE_PARTIAL_INT) |
| return 0; |
| |
| return 1; |
| |
| case CONST: |
| case SYMBOL_REF: |
| case LABEL_REF: |
| return (GET_MODE (op) == mode |
| || mode == SImode || mode == HImode || mode == QImode); |
| |
| case CONST_DOUBLE: |
| return ((GET_MODE_CLASS (mode) == MODE_FLOAT |
| && mode == GET_MODE (op)) |
| || (GET_MODE (op) == VOIDmode |
| && GET_MODE_CLASS (mode) == MODE_INT)); |
| |
| default: |
| return 0; |
| } |
| } |
| |
| /* Return 1 if OP can be used as the output operand for a move insn. */ |
| |
| int |
| out_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| rtx orig_op = op; |
| |
| if (! general_operand (op, mode)) |
| return 0; |
| |
| while (GET_CODE (op) == SUBREG) |
| op = SUBREG_REG (op); |
| |
| if (GET_CODE (op) == REG) |
| return (gpc_reg_operand (orig_op, mode) |
| || spec_reg_operand (orig_op, mode) |
| || (GET_MODE_CLASS (mode) == MODE_FLOAT |
| && accum_reg_operand (orig_op, mode))); |
| |
| else if (GET_CODE (op) == MEM) |
| return (GET_MODE_SIZE (mode) >= UNITS_PER_WORD || TARGET_DW_ENABLE); |
| else |
| return 0; |
| } |
| |
| /* Return 1 if OP is an item in memory, given that we are in reload. */ |
| |
| int |
| reload_memory_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| int regno = true_regnum (op); |
| |
| return (! CONSTANT_P (op) |
| && (regno == -1 |
| || (GET_CODE (op) == REG |
| && REGNO (op) >= FIRST_PSEUDO_REGISTER))); |
| } |
| |
| /* Given an object for which reload_memory_operand is true, return the address |
| of the operand, taking into account anything that reload may do. */ |
| |
| rtx |
| a29k_get_reloaded_address (op) |
| rtx op; |
| { |
| if (GET_CODE (op) == SUBREG) |
| { |
| if (SUBREG_WORD (op) != 0) |
| abort (); |
| |
| op = SUBREG_REG (op); |
| } |
| |
| if (GET_CODE (op) == REG) |
| op = reg_equiv_mem[REGNO (op)]; |
| |
| return find_replacement (&XEXP (op, 0)); |
| } |
| |
| /* Subfunction of the following function. Update the flags of any MEM |
| found in part of X. */ |
| |
| static void |
| a29k_set_memflags_1 (x, in_struct_p, volatile_p, unchanging_p) |
| rtx x; |
| int in_struct_p, volatile_p, unchanging_p; |
| { |
| int i; |
| |
| switch (GET_CODE (x)) |
| { |
| case SEQUENCE: |
| case PARALLEL: |
| for (i = XVECLEN (x, 0) - 1; i >= 0; i--) |
| a29k_set_memflags_1 (XVECEXP (x, 0, i), in_struct_p, volatile_p, |
| unchanging_p); |
| break; |
| |
| case INSN: |
| a29k_set_memflags_1 (PATTERN (x), in_struct_p, volatile_p, |
| unchanging_p); |
| break; |
| |
| case SET: |
| a29k_set_memflags_1 (SET_DEST (x), in_struct_p, volatile_p, |
| unchanging_p); |
| a29k_set_memflags_1 (SET_SRC (x), in_struct_p, volatile_p, unchanging_p); |
| break; |
| |
| case MEM: |
| MEM_IN_STRUCT_P (x) = in_struct_p; |
| MEM_VOLATILE_P (x) = volatile_p; |
| RTX_UNCHANGING_P (x) = unchanging_p; |
| break; |
| } |
| } |
| |
| /* Given INSN, which is either an INSN or a SEQUENCE generated to |
| perform a memory operation, look for any MEMs in either a SET_DEST or |
| a SET_SRC and copy the in-struct, unchanging, and volatile flags from |
| REF into each of the MEMs found. If REF is not a MEM, don't do |
| anything. */ |
| |
| void |
| a29k_set_memflags (insn, ref) |
| rtx insn; |
| rtx ref; |
| { |
| /* Note that it is always safe to get these flags, though they won't |
| be what we think if REF is not a MEM. */ |
| int in_struct_p = MEM_IN_STRUCT_P (ref); |
| int volatile_p = MEM_VOLATILE_P (ref); |
| int unchanging_p = RTX_UNCHANGING_P (ref); |
| |
| if (GET_CODE (ref) != MEM |
| || (! in_struct_p && ! volatile_p && ! unchanging_p)) |
| return; |
| |
| a29k_set_memflags_1 (insn, in_struct_p, volatile_p, unchanging_p); |
| } |
| |
| /* Return 1 if OP is a comparison operator that we have in floating-point. */ |
| |
| int |
| fp_comparison_operator (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| return ((mode == VOIDmode || mode == GET_MODE (op)) |
| && (GET_CODE (op) == EQ || GET_CODE (op) == GT || |
| GET_CODE (op) == GE)); |
| } |
| |
| /* Return 1 if OP is a valid branch comparison. */ |
| |
| int |
| branch_operator (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| return ((mode == VOIDmode || mode == GET_MODE (op)) |
| && (GET_CODE (op) == GE || GET_CODE (op) == LT)); |
| } |
| |
| /* Return 1 if OP is a load multiple operation. It is known to be a |
| PARALLEL and the first three sections will be tested. */ |
| |
| int |
| load_multiple_operation (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| int count = XVECLEN (op, 0) - 2; |
| int dest_regno; |
| rtx src_addr; |
| int i; |
| |
| /* Perform a quick check so we don't blow up below. */ |
| if (count <= 1 |
| || GET_CODE (XVECEXP (op, 0, 0)) != SET |
| || GET_CODE (SET_DEST (XVECEXP (op, 0, 0))) != REG |
| || GET_CODE (SET_SRC (XVECEXP (op, 0, 0))) != MEM) |
| return 0; |
| |
| dest_regno = REGNO (SET_DEST (XVECEXP (op, 0, 0))); |
| src_addr = XEXP (SET_SRC (XVECEXP (op, 0, 0)), 0); |
| |
| for (i = 1; i < count; i++) |
| { |
| rtx elt = XVECEXP (op, 0, i + 2); |
| |
| if (GET_CODE (elt) != SET |
| || GET_CODE (SET_DEST (elt)) != REG |
| || GET_MODE (SET_DEST (elt)) != SImode |
| || REGNO (SET_DEST (elt)) != dest_regno + i |
| || GET_CODE (SET_SRC (elt)) != MEM |
| || GET_MODE (SET_SRC (elt)) != SImode |
| || GET_CODE (XEXP (SET_SRC (elt), 0)) != PLUS |
| || ! rtx_equal_p (XEXP (XEXP (SET_SRC (elt), 0), 0), src_addr) |
| || GET_CODE (XEXP (XEXP (SET_SRC (elt), 0), 1)) != CONST_INT |
| || INTVAL (XEXP (XEXP (SET_SRC (elt), 0), 1)) != i * 4) |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| /* Similar, but tests for store multiple. */ |
| |
| int |
| store_multiple_operation (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| int num_special = TARGET_NO_STOREM_BUG ? 2 : 1; |
| int count = XVECLEN (op, 0) - num_special; |
| int src_regno; |
| rtx dest_addr; |
| int i; |
| |
| /* Perform a quick check so we don't blow up below. */ |
| if (count <= 1 |
| || GET_CODE (XVECEXP (op, 0, 0)) != SET |
| || GET_CODE (SET_DEST (XVECEXP (op, 0, 0))) != MEM |
| || GET_CODE (SET_SRC (XVECEXP (op, 0, 0))) != REG) |
| return 0; |
| |
| src_regno = REGNO (SET_SRC (XVECEXP (op, 0, 0))); |
| dest_addr = XEXP (SET_DEST (XVECEXP (op, 0, 0)), 0); |
| |
| for (i = 1; i < count; i++) |
| { |
| rtx elt = XVECEXP (op, 0, i + num_special); |
| |
| if (GET_CODE (elt) != SET |
| || GET_CODE (SET_SRC (elt)) != REG |
| || GET_MODE (SET_SRC (elt)) != SImode |
| || REGNO (SET_SRC (elt)) != src_regno + i |
| || GET_CODE (SET_DEST (elt)) != MEM |
| || GET_MODE (SET_DEST (elt)) != SImode |
| || GET_CODE (XEXP (SET_DEST (elt), 0)) != PLUS |
| || ! rtx_equal_p (XEXP (XEXP (SET_DEST (elt), 0), 0), dest_addr) |
| || GET_CODE (XEXP (XEXP (SET_DEST (elt), 0), 1)) != CONST_INT |
| || INTVAL (XEXP (XEXP (SET_DEST (elt), 0), 1)) != i * 4) |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| /* Given a special register REG and MASK, a value being masked against a |
| quantity to which the special register is set, return 1 if the masking |
| operation is built-in to the setting of that special register. */ |
| |
| int |
| masks_bits_for_special (reg, mask) |
| rtx reg; |
| rtx mask; |
| { |
| int needed_mask_value; |
| |
| if (GET_CODE (reg) != REG || GET_CODE (mask) != CONST_INT) |
| abort (); |
| |
| switch (REGNO (reg)) |
| { |
| case R_BP: |
| case R_INT: |
| needed_mask_value = 3; |
| break; |
| |
| case R_FC: |
| needed_mask_value = 31; |
| break; |
| |
| case R_CR: |
| case R_LRU: |
| needed_mask_value = 255; |
| break; |
| |
| case R_FPE: |
| needed_mask_value = 511; |
| break; |
| |
| case R_MMU: |
| needed_mask_value = 0x3ff; |
| break; |
| |
| case R_OPS: |
| case R_CPS: |
| case R_RBP: |
| case R_FPS: |
| needed_mask_value = 0xffff; |
| break; |
| |
| case R_VAB: |
| needed_mask_value = 0xffff0000; |
| break; |
| |
| case R_Q: |
| case R_CFG: |
| case R_CHA: |
| case R_CHD: |
| case R_CHC: |
| case R_TMC: |
| case R_TMR: |
| case R_PC0: |
| case R_PC1: |
| case R_PC2: |
| return 0; |
| |
| default: |
| abort (); |
| } |
| |
| return (INTVAL (mask) & ~ needed_mask_value) == 0; |
| } |
| |
| /* Return nonzero if this label is that of the return point, but there is |
| a non-null epilogue. */ |
| |
| int |
| epilogue_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| return next_active_insn (op) == 0 && a29k_first_epilogue_insn != 0; |
| } |
| |
| /* Return the register class of a scratch register needed to copy IN into |
| or out of a register in CLASS in MODE. If it can be done directly, |
| NO_REGS is returned. */ |
| |
| enum reg_class |
| secondary_reload_class (class, mode, in) |
| enum reg_class class; |
| enum machine_mode mode; |
| rtx in; |
| { |
| int regno = -1; |
| enum rtx_code code = GET_CODE (in); |
| |
| if (! CONSTANT_P (in)) |
| { |
| regno = true_regnum (in); |
| |
| /* A pseudo is the same as memory. */ |
| if (regno == -1 || regno >= FIRST_PSEUDO_REGISTER) |
| code = MEM; |
| } |
| |
| /* If we are transferring between memory and a multi-word mode, we need |
| CR. */ |
| |
| if (code == MEM && GET_MODE_SIZE (mode) > UNITS_PER_WORD) |
| return CR_REGS; |
| |
| /* If between memory and a mode smaller than a word without DW being |
| enabled, we need BP. */ |
| |
| if (code == MEM && ! TARGET_DW_ENABLE |
| && GET_MODE_SIZE (mode) < UNITS_PER_WORD) |
| return BP_REGS; |
| |
| /* Otherwise, we can place anything into GENERAL_REGS and can put |
| GENERAL_REGS into anything. */ |
| if (class == GENERAL_REGS |
| || (regno != -1 |
| && (regno < R_BP |
| || (regno >= R_KR (0) && regno <= R_KR (31))))) |
| return NO_REGS; |
| |
| /* We can place 16-bit constants into a special register. */ |
| if (code == CONST_INT |
| && (GET_MODE_BITSIZE (mode) <= 16 || (unsigned) INTVAL (in) <= 65535) |
| && (class == BP_REGS || class == Q_REGS || class == SPECIAL_REGS)) |
| return NO_REGS; |
| |
| /* Otherwise, we need GENERAL_REGS. */ |
| return GENERAL_REGS; |
| } |
| |
| /* START is the zero-based incoming argument register index used (0 is 160, |
| i.e., the first incoming argument register) and COUNT is the number used. |
| |
| Mark the corresponding incoming registers as neither fixed nor call used. |
| For each register used for incoming arguments, we have one less local |
| register that can be used. So also mark some high-numbered registers as |
| fixed. |
| |
| Return the first register number to use for the argument. */ |
| |
| int |
| incoming_reg (start, count) |
| int start; |
| int count; |
| { |
| int i; |
| |
| /* We only use 16 argument registers, so truncate at the end of the |
| area. */ |
| if (start + count > 16) |
| count = 16 - start; |
| |
| if (! TARGET_NO_REUSE_ARGS) |
| /* Mark all the used registers as not fixed and saved over calls. */ |
| for (i = R_AR (start); i < R_AR (start + count); i++) |
| { |
| fixed_regs[i] = call_used_regs[i] = call_fixed_regs[i] = 0; |
| CLEAR_HARD_REG_BIT (fixed_reg_set, i); |
| CLEAR_HARD_REG_BIT (call_used_reg_set, i); |
| CLEAR_HARD_REG_BIT (call_fixed_reg_set, i); |
| } |
| |
| /* Shorten the maximum size of the frame. |
| Remember that R_AR(-1,-2) are place holders for the caller's lr0,lr1. |
| Make sure to keep the frame rounded to an even boundary. Rounding up |
| to an 8 byte boundary will use a slot. Otherwise a frame with 121 local |
| regs and 5 arguments will overrun the stack (121+1 + 5 + 2 > 128). */ |
| /* ??? An alternative would be to never allocate one reg. */ |
| for (i = (R_AR (0) - 2 - start - count) & ~1; i < R_AR (0) - 2 - start; i++) |
| { |
| fixed_regs[i] = call_used_regs[i] = call_fixed_regs[i] = 1; |
| SET_HARD_REG_BIT (fixed_reg_set, i); |
| SET_HARD_REG_BIT (call_used_reg_set, i); |
| SET_HARD_REG_BIT (call_fixed_reg_set, i); |
| } |
| |
| return R_AR (start); |
| } |
| |
| /* Add CLOBBERs to CALL_INSN_FUNCTION_USAGE chain of INSN indicating |
| that LR2 up to, but not including, OP are clobbered. If OP is |
| zero, indicate all parameter registers are clobbered. */ |
| |
| void |
| a29k_clobbers_to (insn, op) |
| rtx insn; |
| rtx op; |
| { |
| int i; |
| int high_regno; |
| |
| if (op == 0) |
| high_regno = R_LR (18); |
| else if (GET_CODE (op) != REG || REGNO (op) < R_LR (0) |
| || REGNO (op) > R_LR (18)) |
| abort (); |
| else |
| high_regno = REGNO (op); |
| |
| for (i = R_LR (2); i < high_regno; i++) |
| CALL_INSN_FUNCTION_USAGE (insn) |
| = gen_rtx (EXPR_LIST, VOIDmode, |
| gen_rtx (CLOBBER, VOIDmode, gen_rtx (REG, SImode, i)), |
| CALL_INSN_FUNCTION_USAGE (insn)); |
| } |
| |
| /* These routines are used in finding insns to fill delay slots in the |
| epilogue. */ |
| |
| /* Return 1 if the current function will adjust the register stack. */ |
| |
| int |
| needs_regstack_p () |
| { |
| int i; |
| rtx insn; |
| |
| if (frame_pointer_needed) |
| return 1; |
| |
| /* If any local register is used, we need to adjust the regstack. */ |
| for (i = R_LR (127); i >= R_LR (0); i --) |
| if (regs_ever_live[i]) |
| return 1; |
| |
| /* We need a register stack if we make any calls. */ |
| for (insn = get_insns (); insn; insn = next_insn (insn)) |
| if (GET_CODE (insn) == CALL_INSN |
| || (GET_CODE (insn) == INSN |
| && GET_CODE (PATTERN (insn)) == SEQUENCE |
| && GET_CODE (XVECEXP (PATTERN (insn), 0, 0)) == CALL_INSN)) |
| return 1; |
| |
| /* Otherwise, we don't. */ |
| return 0; |
| } |
| |
| /* Return 1 if X uses a local register. */ |
| |
| int |
| uses_local_reg_p (x) |
| rtx x; |
| { |
| char *fmt; |
| int i, j; |
| |
| switch (GET_CODE (x)) |
| { |
| case REG: |
| return REGNO (x) >= R_LR (0) && REGNO (x) <= R_FP; |
| |
| case CONST_INT: |
| case CONST: |
| case PC: |
| case CC0: |
| case LABEL_REF: |
| case SYMBOL_REF: |
| return 0; |
| } |
| |
| fmt = GET_RTX_FORMAT (GET_CODE (x)); |
| for (i = GET_RTX_LENGTH (GET_CODE (x)) - 1; i >= 0; i--) |
| { |
| if (fmt[i] == 'e') |
| { |
| if (uses_local_reg_p (XEXP (x, i))) |
| return 1; |
| } |
| else if (fmt[i] == 'E') |
| { |
| for (j = XVECLEN (x, i) - 1; j >= 0; j--) |
| if (uses_local_reg_p (XVECEXP (x, i, j))) |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* Returns 1 if this function is known to have a null epilogue. */ |
| |
| int |
| null_epilogue () |
| { |
| return (reload_completed && ! needs_regstack_p () |
| && get_frame_size () == 0 |
| && current_function_pretend_args_size == 0); |
| } |
| |
| /* Write out the assembler form of an operand. Recognize the following |
| special options: |
| |
| %N means write the low-order 8 bits of the negative of the constant |
| %Q means write a QImode operand (truncate constants to 8 bits) |
| %M means write the low-order 16 bits of the constant |
| %m means write the low-order 16 bits shifted left 16 bits |
| %C means write the low-order 8 bits of the complement of the constant |
| %b means write `f' is this is a reversed condition, `t' otherwise |
| %B means write `t' is this is a reversed condition, `f' otherwise |
| %J means write the 29k opcode part for a comparison operation |
| %e means write the label with an extra `X' is this is the epilogue |
| otherwise the normal label name |
| %E means write nothing if this insn has a delay slot, |
| a nop unless this is the epilogue label, in which case |
| write the first epilogue insn |
| %F means write just the normal operand if the insn has a delay slot; |
| otherwise, this is a recursive call so output the |
| symbol + 4 and write the first prologue insn in the |
| delay slot. |
| %L means write the register number plus one ("low order" register) |
| or the low-order part of a multi-word constant |
| %O means write the register number plus two |
| %P means write the register number plus three ("low order" of TImode) |
| %S means write the number of words in the mode of the operand, |
| minus one (for CR) |
| %V means write the number of elements in a PARALLEL minus 1 |
| %# means write nothing if we have a delay slot, "\n\tnop" otherwise |
| %* means write the register name for TPC. */ |
| |
| void |
| print_operand (file, x, code) |
| FILE *file; |
| rtx x; |
| char code; |
| { |
| char buf[100]; |
| |
| /* These macros test for integers and extract the low-order bits. */ |
| #define INT_P(X) \ |
| ((GET_CODE (X) == CONST_INT || GET_CODE (X) == CONST_DOUBLE) \ |
| && GET_MODE (X) == VOIDmode) |
| |
| #define INT_LOWPART(X) \ |
| (GET_CODE (X) == CONST_INT ? INTVAL (X) : CONST_DOUBLE_LOW (X)) |
| |
| switch (code) |
| { |
| case 'Q': |
| if (GET_CODE (x) == REG) |
| break; |
| else if (! INT_P (x)) |
| output_operand_lossage ("invalid %%Q value"); |
| fprintf (file, "%d", INT_LOWPART (x) & 0xff); |
| return; |
| |
| case 'C': |
| if (! INT_P (x)) |
| output_operand_lossage ("invalid %%C value"); |
| fprintf (file, "%d", (~ INT_LOWPART (x)) & 0xff); |
| return; |
| |
| case 'N': |
| if (! INT_P (x)) |
| output_operand_lossage ("invalid %%N value"); |
| fprintf (file, "%d", (- INT_LOWPART (x)) & 0xff); |
| return; |
| |
| case 'M': |
| if (! INT_P (x)) |
| output_operand_lossage ("invalid %%M value"); |
| fprintf (file, "%d", INT_LOWPART (x) & 0xffff); |
| return; |
| |
| case 'm': |
| if (! INT_P (x)) |
| output_operand_lossage ("invalid %%m value"); |
| fprintf (file, "%d", (INT_LOWPART (x) & 0xffff) << 16); |
| return; |
| |
| case 'b': |
| if (GET_CODE (x) == GE) |
| fprintf (file, "f"); |
| else |
| fprintf (file, "t"); |
| return; |
| |
| case 'B': |
| if (GET_CODE (x) == GE) |
| fprintf (file, "t"); |
| else |
| fprintf (file, "f"); |
| return; |
| |
| case 'J': |
| /* It so happens that the RTX names for the conditions are the same as |
| the 29k's insns except for "ne", which requires "neq". */ |
| fprintf (file, GET_RTX_NAME (GET_CODE (x))); |
| if (GET_CODE (x) == NE) |
| fprintf (file, "q"); |
| return; |
| |
| case 'e': |
| if (optimize && flag_delayed_branch |
| && a29k_last_prologue_insn == 0 && epilogue_operand (x, VOIDmode) |
| && dbr_sequence_length () == 0) |
| { |
| /* We need to output the label number of the last label in the |
| function, which is not necessarily X since there might be |
| a USE insn in between. First go forward to the last insn, then |
| back up to a label. */ |
| while (NEXT_INSN (x) != 0) |
| x = NEXT_INSN (x); |
| |
| while (GET_CODE (x) != CODE_LABEL) |
| x = PREV_INSN (x); |
| |
| ASM_GENERATE_INTERNAL_LABEL (buf, "LX", CODE_LABEL_NUMBER (x)); |
| assemble_name (file, buf); |
| } |
| else |
| output_asm_label (x); |
| return; |
| |
| case 'E': |
| if (dbr_sequence_length ()) |
| ; |
| else if (a29k_last_prologue_insn) |
| { |
| fprintf (file, "\n\t%s", a29k_last_prologue_insn); |
| a29k_last_prologue_insn = 0; |
| } |
| else if (optimize && flag_delayed_branch |
| && epilogue_operand (x, VOIDmode)) |
| { |
| fprintf (file, "\n\t%s", a29k_first_epilogue_insn); |
| a29k_first_epilogue_insn_used = 1; |
| } |
| else |
| fprintf (file, "\n\tnop"); |
| return; |
| |
| case 'F': |
| output_addr_const (file, x); |
| if (dbr_sequence_length () == 0) |
| { |
| /* If this doesn't have its delay slot filled, see if we need to |
| put the last insn of the prolog in it. If not, see if this is |
| a recursive call. If so, we can put the first insn of its |
| prolog in the delay slot. Otherwise, write a nop. */ |
| if (a29k_last_prologue_insn) |
| { |
| fprintf (file, "\n\t%s", a29k_last_prologue_insn); |
| a29k_last_prologue_insn = 0; |
| } |
| else if (GET_CODE (x) == SYMBOL_REF |
| && ! strcmp (XSTR (x, 0), current_function_name)) |
| fprintf (file, "+4\n\t%s,%d", |
| a29k_regstack_size >= 64 ? "const gr121" : "sub gr1,gr1", |
| a29k_regstack_size * 4); |
| else |
| fprintf (file, "\n\tnop"); |
| } |
| return; |
| |
| case 'L': |
| if (GET_CODE (x) == CONST_DOUBLE && GET_MODE (x) == DFmode) |
| { |
| union real_extract u; |
| |
| bcopy ((char *) &CONST_DOUBLE_LOW (x), (char *) &u, sizeof u); |
| fprintf (file, "$double1(%.20e)", u.d); |
| } |
| else if (GET_CODE (x) == REG) |
| fprintf (file, "%s", reg_names[REGNO (x) + 1]); |
| else |
| output_operand_lossage ("invalid %%L value"); |
| return; |
| |
| case 'O': |
| if (GET_CODE (x) != REG) |
| output_operand_lossage ("invalid %%O value"); |
| fprintf (file, "%s", reg_names[REGNO (x) + 2]); |
| return; |
| |
| case 'P': |
| if (GET_CODE (x) != REG) |
| output_operand_lossage ("invalid %%P value"); |
| fprintf (file, "%s", reg_names[REGNO (x) + 3]); |
| return; |
| |
| case 'S': |
| fprintf (file, "%d", (GET_MODE_SIZE (GET_MODE (x)) / UNITS_PER_WORD)-1); |
| return; |
| |
| case 'V': |
| if (GET_CODE (x) != PARALLEL) |
| output_operand_lossage ("invalid %%V value"); |
| fprintf (file, "%d", XVECLEN (x, 0) - 2); |
| return; |
| |
| case '#': |
| if (dbr_sequence_length () == 0) |
| { |
| if (a29k_last_prologue_insn) |
| { |
| fprintf (file, "\n\t%s", a29k_last_prologue_insn); |
| a29k_last_prologue_insn = 0; |
| } |
| else |
| fprintf (file, "\n\tnop"); |
| } |
| return; |
| |
| case '*': |
| fprintf (file, "%s", reg_names [R_TPC]); |
| return; |
| } |
| |
| if (GET_CODE (x) == REG) |
| fprintf (file, "%s", reg_names [REGNO (x)]); |
| |
| else if (GET_CODE (x) == MEM) |
| output_address (XEXP (x, 0)); |
| |
| else if (GET_CODE (x) == CONST && GET_CODE (XEXP (x, 0)) == SUBREG |
| && GET_CODE (SUBREG_REG (XEXP (x, 0))) == CONST_DOUBLE) |
| { |
| union real_extract u; |
| |
| if (GET_MODE (SUBREG_REG (XEXP (x, 0))) == SFmode) |
| fprintf (file, "$float"); |
| else |
| fprintf (file, "$double%d", SUBREG_WORD (XEXP (x, 0))); |
| bcopy ((char *) &CONST_DOUBLE_LOW (SUBREG_REG (XEXP (x, 0))), |
| (char *) &u, sizeof u); |
| fprintf (file, "(%.20e)", u.d); |
| } |
| |
| else if (GET_CODE (x) == CONST_DOUBLE |
| && GET_MODE_CLASS (GET_MODE (x)) == MODE_FLOAT) |
| { |
| union real_extract u; |
| |
| bcopy ((char *) &CONST_DOUBLE_LOW (x), (char *) &u, sizeof u); |
| fprintf (file, "$%s(%.20e)", |
| GET_MODE (x) == SFmode ? "float" : "double0", u.d); |
| } |
| |
| else |
| output_addr_const (file, x); |
| } |
| |
| /* This page contains routines to output function prolog and epilog code. */ |
| |
| /* Compute the size of the register stack, and determine if there are any |
| call instructions. */ |
| |
| static void |
| compute_regstack_size () |
| { |
| int i; |
| rtx insn; |
| |
| /* See if we make any calls. We need to set lr1 if so. */ |
| a29k_makes_calls = 0; |
| for (insn = get_insns (); insn; insn = next_insn (insn)) |
| if (GET_CODE (insn) == CALL_INSN |
| || (GET_CODE (insn) == INSN |
| && GET_CODE (PATTERN (insn)) == SEQUENCE |
| && GET_CODE (XVECEXP (PATTERN (insn), 0, 0)) == CALL_INSN)) |
| { |
| a29k_makes_calls = 1; |
| break; |
| } |
| |
| /* Find the highest local register used. */ |
| for (i = R_LR (127); i >= R_LR (0); i--) |
| if (regs_ever_live[i]) |
| break; |
| |
| a29k_regstack_size = i - (R_LR (0) - 1); |
| |
| /* If calling routines, ensure we count lr0 & lr1. */ |
| if (a29k_makes_calls && a29k_regstack_size < 2) |
| a29k_regstack_size = 2; |
| |
| /* Count frame pointer and align to 8 byte boundary (even number of |
| registers). */ |
| a29k_regstack_size += frame_pointer_needed; |
| if (a29k_regstack_size & 1) a29k_regstack_size++; |
| } |
| |
| /* Sets register names for incoming arguments and frame pointer. |
| This can't be computed until after register allocation. */ |
| |
| void |
| a29k_compute_reg_names () |
| { |
| int i; |
| |
| compute_regstack_size (); |
| |
| /* Set the names and numbers of the frame pointer and incoming argument |
| registers. */ |
| |
| for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) |
| a29k_debug_reg_map[i] = i; |
| |
| reg_names[FRAME_POINTER_REGNUM] = reg_names[R_LR (a29k_regstack_size - 1)]; |
| a29k_debug_reg_map[FRAME_POINTER_REGNUM] = R_LR (a29k_regstack_size - 1); |
| |
| for (i = 0; i < 16; i++) |
| { |
| reg_names[R_AR (i)] = reg_names[R_LR (a29k_regstack_size + i + 2)]; |
| a29k_debug_reg_map[R_AR (i)] = R_LR (a29k_regstack_size + i + 2); |
| } |
| |
| /* If using kernel register map, swap numbers for kernel and user |
| registers. */ |
| if (TARGET_KERNEL_REGISTERS) |
| for (i = 0; i < 32; i++) |
| { |
| int tem = a29k_debug_reg_map[i]; |
| a29k_debug_reg_map[i] = a29k_debug_reg_map[R_KR (i)]; |
| a29k_debug_reg_map[R_KR (i)] = tem; |
| } |
| } |
| |
| /* Output function prolog code to file FILE. Memory stack size is SIZE. */ |
| |
| void |
| output_prolog (file, size) |
| FILE *file; |
| int size; |
| { |
| int i; |
| int arg_count = 0; |
| rtx insn; |
| unsigned int tag_word; |
| |
| /* See how many incoming arguments we have in registers. */ |
| for (i = R_AR (0); i < R_AR (16); i++) |
| if (! fixed_regs[i]) |
| arg_count++; |
| |
| /* The argument count includes the caller's lr0 and lr1. */ |
| arg_count += 2; |
| |
| /* Compute memory stack size. Add in number of bytes that the we should |
| push and pretend the caller did and the size of outgoing arguments. |
| Then round to a doubleword boundary. */ |
| size += (current_function_pretend_args_size |
| + current_function_outgoing_args_size); |
| size = (size + 7) & ~7; |
| |
| /* Write header words. See if one or two word form. */ |
| tag_word = (frame_pointer_needed ? 0x400000 : 0) + (arg_count << 16); |
| |
| if (size / 8 > 0xff) |
| fprintf (file, "\t.word %d, 0x%0x\n", (size / 8) << 2, |
| 0x800000 + tag_word); |
| else |
| fprintf (file, "\t.word 0x%0x\n", tag_word + ((size / 8) << 3)); |
| |
| /* Define the function name. */ |
| assemble_name (file, a29k_function_name); |
| fprintf (file, ":\n"); |
| |
| /* Push the register stack by the proper amount. There are two possible |
| ways to do this. */ |
| if (a29k_regstack_size >= 256/4) |
| fprintf (file, "\tconst %s,%d\n\tsub gr1,gr1,%s\n", |
| reg_names[R_TAV], a29k_regstack_size * 4, reg_names[R_TAV]); |
| else if (a29k_regstack_size) |
| fprintf (file, "\tsub gr1,gr1,%d\n", a29k_regstack_size * 4); |
| |
| /* Test that the registers are available. */ |
| if (a29k_regstack_size) |
| fprintf (file, "\tasgeu V_%sSPILL,gr1,%s\n", |
| TARGET_KERNEL_REGISTERS ? "K" : "", reg_names[R_RAB]); |
| |
| /* Set up frame pointer, if one is needed. */ |
| if (frame_pointer_needed) |
| fprintf (file, "\tsll %s,%s,0\n", reg_names[FRAME_POINTER_REGNUM], |
| reg_names[R_MSP]); |
| |
| /* Make room for any frame space. There are three ways to do this. */ |
| if (size >= 256) |
| { |
| fprintf (file, "\tconst %s,%d\n", reg_names[R_TAV], size); |
| if (size >= 65536) |
| fprintf (file, "\tconsth %s,%d\n", reg_names[R_TAV], size); |
| if (TARGET_STACK_CHECK) |
| fprintf (file, "\tcall %s,__msp_check\n", reg_names[R_TPC]); |
| fprintf (file, "\tsub %s,%s,%s\n", |
| reg_names[R_MSP], reg_names[R_MSP], reg_names[R_TAV]); |
| } |
| else if (size) |
| { |
| if (TARGET_STACK_CHECK) |
| fprintf (file, "\tcall %s,__msp_check\n", reg_names[R_TPC]); |
| fprintf (file, "\tsub %s,%s,%d\n", |
| reg_names[R_MSP], reg_names[R_MSP], size); |
| } |
| |
| /* If this routine will make calls, set lr1. If we see an insn that |
| can use a delay slot before a call or jump, save this insn for that |
| slot (this condition is equivalent to seeing if we have an insn that |
| needs delay slots before an insn that has a filled delay slot). */ |
| a29k_last_prologue_insn = 0; |
| if (a29k_makes_calls) |
| { |
| i = (a29k_regstack_size + arg_count) * 4; |
| if (i >= 256) |
| fprintf (file, "\tconst %s,%d\n\tadd lr1,gr1,%s\n", |
| reg_names[R_TAV], i, reg_names[R_TAV]); |
| else |
| { |
| if (optimize && flag_delayed_branch) |
| for (insn = get_insns (); insn; insn = NEXT_INSN (insn)) |
| { |
| if (GET_CODE (insn) == CODE_LABEL |
| || (GET_CODE (insn) == INSN |
| && GET_CODE (PATTERN (insn)) == SEQUENCE)) |
| break; |
| |
| if (GET_CODE (insn) == NOTE |
| || (GET_CODE (insn) == INSN |
| && (GET_CODE (PATTERN (insn)) == USE |
| || GET_CODE (PATTERN (insn)) == CLOBBER))) |
| continue; |
| |
| if (num_delay_slots (insn) > 0) |
| { |
| a29k_last_prologue_insn = (char *) oballoc (100); |
| sprintf (a29k_last_prologue_insn, "add lr1,gr1,%d", i); |
| break; |
| } |
| } |
| |
| if (a29k_last_prologue_insn == 0) |
| fprintf (file, "\tadd lr1,gr1,%d\n", i); |
| } |
| } |
| |
| /* Compute the first insn of the epilogue. */ |
| a29k_first_epilogue_insn_used = 0; |
| |
| if (size == 0 && a29k_regstack_size == 0 && ! frame_pointer_needed) |
| a29k_first_epilogue_insn = 0; |
| else |
| a29k_first_epilogue_insn = (char *) oballoc (100); |
| |
| if (frame_pointer_needed) |
| sprintf (a29k_first_epilogue_insn, "sll %s,%s,0", |
| reg_names[R_MSP], reg_names[FRAME_POINTER_REGNUM]); |
| else if (a29k_regstack_size) |
| { |
| if (a29k_regstack_size >= 256 / 4) |
| sprintf (a29k_first_epilogue_insn, "const %s,%d", |
| reg_names[R_TAV], a29k_regstack_size * 4); |
| else |
| sprintf (a29k_first_epilogue_insn, "add gr1,gr1,%d", |
| a29k_regstack_size * 4); |
| } |
| else if (size) |
| { |
| if (size >= 256) |
| sprintf (a29k_first_epilogue_insn, "const %s,%d", |
| reg_names[R_TAV], size); |
| else |
| sprintf (a29k_first_epilogue_insn, "add %s,%s,%d", |
| reg_names[R_MSP], reg_names[R_MSP], size); |
| } |
| } |
| |
| /* Call this after writing what might be the first instruction of the |
| epilogue. If that first insn was used in a delay slot, an intermediate |
| label is written. */ |
| |
| static void |
| check_epilogue_internal_label (file) |
| FILE *file; |
| { |
| rtx insn; |
| |
| if (! a29k_first_epilogue_insn_used) |
| return; |
| |
| for (insn = get_last_insn (); |
| GET_CODE (insn) != CODE_LABEL; |
| insn = PREV_INSN (insn)) |
| ; |
| |
| ASM_OUTPUT_INTERNAL_LABEL (file, "LX", CODE_LABEL_NUMBER (insn)); |
| a29k_first_epilogue_insn_used = 0; |
| } |
| |
| /* Output the epilog of the last procedure to file FILE. SIZE is the memory |
| stack size. The register stack size is in the variable |
| A29K_REGSTACK_SIZE. */ |
| |
| void |
| output_epilog (file, size) |
| FILE *file; |
| int size; |
| { |
| rtx insn; |
| int locals_unavailable = 0; /* True until after first insn |
| after gr1 update. */ |
| |
| /* If we hit a BARRIER before a real insn or CODE_LABEL, we don't |
| need to do anything because we are never jumped to. */ |
| insn = get_last_insn (); |
| if (GET_CODE (insn) == NOTE) |
| insn = prev_nonnote_insn (insn); |
| |
| if (insn && GET_CODE (insn) == BARRIER) |
| return; |
| |
| /* If a frame pointer was needed we must restore the memory stack pointer |
| before adjusting the register stack. */ |
| if (frame_pointer_needed) |
| { |
| fprintf (file, "\tsll %s,%s,0\n", |
| reg_names[R_MSP], reg_names[FRAME_POINTER_REGNUM]); |
| check_epilogue_internal_label (file); |
| } |
| |
| /* Restore the register stack. There are two ways to do this. */ |
| if (a29k_regstack_size) |
| { |
| if (a29k_regstack_size >= 256/4) |
| { |
| fprintf (file, "\tconst %s,%d\n", |
| reg_names[R_TAV], a29k_regstack_size * 4); |
| check_epilogue_internal_label (file); |
| fprintf (file, "\tadd gr1,gr1,%s\n", reg_names[R_TAV]); |
| } |
| else |
| { |
| fprintf (file, "\tadd gr1,gr1,%d\n", a29k_regstack_size * 4); |
| check_epilogue_internal_label (file); |
| } |
| locals_unavailable = 1; |
| } |
| |
| /* Restore the memory stack pointer if there is no frame pointer. |
| Adjust the size to include any pretend arguments and pushed |
| arguments and round to doubleword boundary. */ |
| size += (current_function_pretend_args_size |
| + current_function_outgoing_args_size); |
| size = (size + 7) & ~7; |
| |
| if (size && ! frame_pointer_needed) |
| { |
| if (size >= 256) |
| { |
| fprintf (file, "\tconst %s,%d\n", reg_names[R_TAV], size); |
| check_epilogue_internal_label (file); |
| locals_unavailable = 0; |
| if (size >= 65536) |
| fprintf (file, "\tconsth %s,%d\n", reg_names[R_TAV], size); |
| fprintf (file, "\tadd %s,%s,%s\n", |
| reg_names[R_MSP], reg_names[R_MSP], reg_names[R_TAV]); |
| } |
| else |
| { |
| fprintf (file, "\tadd %s,%s,%d\n", |
| reg_names[R_MSP], reg_names[R_MSP], size); |
| check_epilogue_internal_label (file); |
| locals_unavailable = 0; |
| } |
| } |
| |
| if (locals_unavailable) |
| { |
| /* If we have an insn for this delay slot, write it. */ |
| if (current_function_epilogue_delay_list) |
| final_scan_insn (XEXP (current_function_epilogue_delay_list, 0), |
| file, 1, -2, 1); |
| else |
| fprintf (file, "\tnop\n"); |
| } |
| |
| fprintf (file, "\tjmpi lr0\n"); |
| if (a29k_regstack_size) |
| fprintf (file, "\tasleu V_%sFILL,lr1,%s\n", |
| TARGET_KERNEL_REGISTERS ? "K" : "", reg_names[R_RFB]); |
| else if (current_function_epilogue_delay_list) |
| final_scan_insn (XEXP (current_function_epilogue_delay_list, 0), |
| file, 1, -2, 1); |
| else |
| fprintf (file, "\tnop\n"); |
| } |