--- gcc/config/arm/arm.c 2018/04/24 18:10:26 1.1.1.1 +++ gcc/config/arm/arm.c 2018/04/24 18:22:26 1.1.1.3 @@ -1,7 +1,8 @@ /* Output routines for GCC for ARM/RISCiX. - Copyright (C) 1991 Free Software Foundation, Inc. + Copyright (C) 1991, 1993, 1994 Free Software Foundation, Inc. Contributed by Pieter `Tiggr' Schoenmakers (rcpieter@win.tue.nl) and Martin Simmons (@harleqn.co.uk). + More major hacks by Richard Earnshaw (rwe11@cl.cam.ac.uk) This file is part of GNU CC. @@ -18,8 +19,9 @@ GNU General Public License for more deta 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 +#include #include "assert.h" #include "config.h" #include "rtl.h" @@ -32,6 +34,9 @@ the Free Software Foundation, 675 Mass A #include "output.h" #include "insn-attr.h" #include "flags.h" +#include "reload.h" +#include "tree.h" +#include "expr.h" /* The maximum number of insns skipped which will be conditionalised if possible. */ @@ -40,13 +45,27 @@ the Free Software Foundation, 675 Mass A /* Some function declarations. */ extern FILE *asm_out_file; extern char *output_multi_immediate (); -extern char *arm_output_asm_insn (); extern void arm_increase_location (); +HOST_WIDE_INT int_log2 PROTO ((HOST_WIDE_INT)); +static int get_prologue_size PROTO ((void)); + +/* Define the information needed to generate branch insns. This is + stored from the compare operation. */ + +rtx arm_compare_op0, arm_compare_op1; +int arm_compare_fp; + +/* What type of cpu are we compiling for? */ +enum processor_type arm_cpu; + +/* Waht type of floating point are we compiling for? */ +enum floating_point_type arm_fpu; + /* 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; +enum machine_mode output_memory_reference_mode; /* Nonzero if the prologue must setup `fp'. */ int current_function_anonymous_args; @@ -54,6 +73,10 @@ int current_function_anonymous_args; /* Location counter of .text segment. */ int arm_text_location = 0; +/* Set to one if we think that lr is only saved because of subroutine calls, + but all of these can be `put after' return insns */ +int lr_save_eliminated; + /* A hash table is used to store text segment labels and their associated offset from the start of the text segment. */ struct label_offset @@ -67,48 +90,830 @@ struct label_offset static struct label_offset *offset_table[LABEL_HASH_SIZE]; +/* Set to 1 when a return insn is output, this means that the epilogue + is not needed. */ + +static int return_used_this_function; + /* 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[]; + +/* 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) -/* Return the number of mov instructions needed to get the constant VALUE into - a register. */ +/* Return 1 if it is possible to return using a single instruction */ int -arm_const_nmoves (value) - register int value; +use_return_insn () { - register int i; + int regno; - 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 */ + if (!reload_completed ||current_function_pretend_args_size + || current_function_anonymous_args + || (get_frame_size () && !(TARGET_APCS || frame_pointer_needed))) + return 0; + + /* Can't be done if any of the FPU regs are pushed, since this also + requires an insn */ + for (regno = 20; regno < 24; regno++) + if (regs_ever_live[regno]) + return 0; + return 1; +} /* Return TRUE if int I is a valid immediate ARM constant. */ int const_ok_for_arm (i) - int i; + HOST_WIDE_INT i; { - unsigned int mask = ~0xFF; + unsigned HOST_WIDE_INT mask = ~0xFF; + + /* Fast return for 0 and powers of 2 */ + if ((i & (i - 1)) == 0) + return TRUE; do { - if ((i & mask) == 0) - return(TRUE); - mask = (mask << 2) | (mask >> (32 - 2)); + if ((i & mask & (unsigned HOST_WIDE_INT) 0xffffffff) == 0) + return TRUE; + mask = + (mask << 2) | ((mask & (unsigned HOST_WIDE_INT) 0xffffffff) + >> (32 - 2)) | ~((unsigned HOST_WIDE_INT) 0xffffffff); } while (mask != ~0xFF); - return (FALSE); -} /* const_ok_for_arm */ + return FALSE; +} + +/* Return true if I is a valid constant for the operation CODE. */ +int +const_ok_for_op (i, code, mode) + HOST_WIDE_INT i; + enum rtx_code code; + enum machine_mode mode; +{ + if (const_ok_for_arm (i)) + return 1; + + switch (code) + { + case PLUS: + return const_ok_for_arm (ARM_SIGN_EXTEND (-i)); + + case MINUS: /* Should only occur with (MINUS I reg) => rsb */ + case XOR: + case IOR: + return 0; + + case AND: + return const_ok_for_arm (ARM_SIGN_EXTEND (~i)); + + default: + abort (); + } +} + +/* Emit a sequence of insns to handle a large constant. + CODE is the code of the operation required, it can be any of SET, PLUS, + IOR, AND, XOR, MINUS; + MODE is the mode in which the operation is being performed; + VAL is the integer to operate on; + SOURCE is the other operand (a register, or a null-pointer for SET); + SUBTARGETS means it is safe to create scratch registers if that will + either produce a simpler sequence, or we will want to cse the values. */ + +int +arm_split_constant (code, mode, val, target, source, subtargets) + enum rtx_code code; + enum machine_mode mode; + HOST_WIDE_INT val; + rtx target; + rtx source; + int subtargets; +{ + int can_add = 0; + int can_invert = 0; + int can_negate = 0; + int can_negate_initial = 0; + int can_shift = 0; + int i; + int num_bits_set = 0; + int set_sign_bit_copies = 0; + int clear_sign_bit_copies = 0; + int clear_zero_bit_copies = 0; + int set_zero_bit_copies = 0; + int insns = 0; + rtx new_src; + unsigned HOST_WIDE_INT temp1, temp2; + unsigned HOST_WIDE_INT remainder = val & 0xffffffff; + + /* find out which operations are safe for a given CODE. Also do a quick + check for degenerate cases; these can occur when DImode operations + are split. */ + switch (code) + { + case SET: + can_invert = 1; + can_shift = 1; + can_negate = 1; + break; + + case PLUS: + can_negate = 1; + can_negate_initial = 1; + break; + + case IOR: + if (remainder == 0xffffffff) + { + emit_insn (gen_rtx (SET, VOIDmode, target, + GEN_INT (ARM_SIGN_EXTEND (val)))); + return 1; + } + if (remainder == 0) + { + if (reload_completed && rtx_equal_p (target, source)) + return 0; + emit_insn (gen_rtx (SET, VOIDmode, target, source)); + return 1; + } + break; + + case AND: + if (remainder == 0) + { + emit_insn (gen_rtx (SET, VOIDmode, target, const0_rtx)); + return 1; + } + if (remainder == 0xffffffff) + { + if (reload_completed && rtx_equal_p (target, source)) + return 0; + emit_insn (gen_rtx (SET, VOIDmode, target, source)); + return 1; + } + can_invert = 1; + break; + + case XOR: + if (remainder == 0) + { + if (reload_completed && rtx_equal_p (target, source)) + return 0; + emit_insn (gen_rtx (SET, VOIDmode, target, source)); + return 1; + } + if (remainder == 0xffffffff) + { + emit_insn (gen_rtx (SET, VOIDmode, target, + gen_rtx (NOT, mode, source))); + return 1; + } + + /* We don't know how to handle this yet below. */ + abort (); + + case MINUS: + /* We treat MINUS as (val - source), since (source - val) is always + passed as (source + (-val)). */ + if (remainder == 0) + { + emit_insn (gen_rtx (SET, VOIDmode, target, + gen_rtx (NEG, mode, source))); + return 1; + } + if (const_ok_for_arm (val)) + { + emit_insn (gen_rtx (SET, VOIDmode, target, + gen_rtx (MINUS, mode, GEN_INT (val), source))); + return 1; + } + can_negate = 1; + + break; + + default: + abort (); + } + + /* If we can do it in one insn get out quickly */ + if (const_ok_for_arm (val) + || (can_negate_initial && const_ok_for_arm (-val)) + || (can_invert && const_ok_for_arm (~val))) + { + emit_insn (gen_rtx (SET, VOIDmode, target, + (source ? gen_rtx (code, mode, source, + GEN_INT (val)) : GEN_INT (val)))); + return 1; + } + + + /* Calculate a few attributes that may be useful for specific + optimizations. */ + + for (i = 31; i >= 0; i--) + { + if ((remainder & (1 << i)) == 0) + clear_sign_bit_copies++; + else + break; + } + + for (i = 31; i >= 0; i--) + { + if ((remainder & (1 << i)) != 0) + set_sign_bit_copies++; + else + break; + } + + for (i = 0; i <= 31; i++) + { + if ((remainder & (1 << i)) == 0) + clear_zero_bit_copies++; + else + break; + } + + for (i = 0; i <= 31; i++) + { + if ((remainder & (1 << i)) != 0) + set_zero_bit_copies++; + else + break; + } + + switch (code) + { + case SET: + /* See if we can do this by sign_extending a constant that is known + to be negative. This is a good, way of doing it, since the shift + may well merge into a subsequent insn. */ + if (set_sign_bit_copies > 1) + { + if (const_ok_for_arm + (temp1 = ARM_SIGN_EXTEND (remainder + << (set_sign_bit_copies - 1)))) + { + new_src = subtargets ? gen_reg_rtx (mode) : target; + emit_insn (gen_rtx (SET, VOIDmode, new_src, GEN_INT (temp1))); + emit_insn (gen_ashrsi3 (target, new_src, + GEN_INT (set_sign_bit_copies - 1))); + return 2; + } + /* For an inverted constant, we will need to set the low bits, + these will be shifted out of harm's way. */ + temp1 |= (1 << (set_sign_bit_copies - 1)) - 1; + if (const_ok_for_arm (~temp1)) + { + new_src = subtargets ? gen_reg_rtx (mode) : target; + emit_insn (gen_rtx (SET, VOIDmode, new_src, GEN_INT (temp1))); + emit_insn (gen_ashrsi3 (target, new_src, + GEN_INT (set_sign_bit_copies - 1))); + return 2; + } + } + + /* See if we can generate this by setting the bottom (or the top) + 16 bits, and then shifting these into the other half of the + word. We only look for the simplest cases, to do more would cost + too much. Be careful, however, not to generate this when the + alternative would take fewer insns. */ + if (val & 0xffff0000) + { + temp1 = remainder & 0xffff0000; + temp2 = remainder & 0x0000ffff; + + /* Overlaps outside this range are best done using other methods. */ + for (i = 9; i < 24; i++) + { + if ((((temp2 | (temp2 << i)) & 0xffffffff) == remainder) + && ! const_ok_for_arm (temp2)) + { + insns + = arm_split_constant (code, mode, temp2, + (new_src + = subtargets ? gen_reg_rtx (mode) + : target), + source, subtargets); + source = new_src; + emit_insn (gen_rtx (SET, VOIDmode, target, + gen_rtx (IOR, mode, + gen_rtx (ASHIFT, mode, source, + GEN_INT (i)), + source))); + return insns + 1; + } + } + + /* Don't duplicate cases already considered. */ + for (i = 17; i < 24; i++) + { + if (((temp1 | (temp1 >> i)) == remainder) + && ! const_ok_for_arm (temp1)) + { + insns + = arm_split_constant (code, mode, temp1, + (new_src + = subtargets ? gen_reg_rtx (mode) + : target), + source, subtargets); + source = new_src; + emit_insn (gen_rtx (SET, VOIDmode, target, + gen_rtx (IOR, mode, + gen_rtx (LSHIFTRT, mode, source, + GEN_INT (i)), + source))); + return insns + 1; + } + } + } + break; + + case IOR: + case XOR: + /* If we have IOR or XOR, and the inverse of the constant can be loaded + in a single instruction, and we can find a temporary to put it in, + then this can be done in two instructions instead of 3-4. */ + if (subtargets + || (reload_completed && ! reg_mentioned_p (target, source))) + { + if (const_ok_for_arm (ARM_SIGN_EXTEND (~ val))) + { + rtx sub = subtargets ? gen_reg_rtx (mode) : target; + + emit_insn (gen_rtx (SET, VOIDmode, sub, + GEN_INT (ARM_SIGN_EXTEND (~ val)))); + emit_insn (gen_rtx (SET, VOIDmode, target, + gen_rtx (code, mode, source, sub))); + return 2; + } + } + + if (code == XOR) + break; + + if (set_sign_bit_copies > 8 + && (val & (-1 << (32 - set_sign_bit_copies))) == val) + { + rtx sub = subtargets ? gen_reg_rtx (mode) : target; + rtx shift = GEN_INT (set_sign_bit_copies); + + emit_insn (gen_rtx (SET, VOIDmode, sub, + gen_rtx (NOT, mode, + gen_rtx (ASHIFT, mode, source, + shift)))); + emit_insn (gen_rtx (SET, VOIDmode, target, + gen_rtx (NOT, mode, + gen_rtx (LSHIFTRT, mode, sub, + shift)))); + return 2; + } + + if (set_zero_bit_copies > 8 + && (remainder & ((1 << set_zero_bit_copies) - 1)) == remainder) + { + rtx sub = subtargets ? gen_reg_rtx (mode) : target; + rtx shift = GEN_INT (set_zero_bit_copies); + + emit_insn (gen_rtx (SET, VOIDmode, sub, + gen_rtx (NOT, mode, + gen_rtx (LSHIFTRT, mode, source, + shift)))); + emit_insn (gen_rtx (SET, VOIDmode, target, + gen_rtx (NOT, mode, + gen_rtx (ASHIFT, mode, sub, + shift)))); + return 2; + } + + if (const_ok_for_arm (temp1 = ARM_SIGN_EXTEND (~ val))) + { + rtx sub = subtargets ? gen_reg_rtx (mode) : target; + emit_insn (gen_rtx (SET, VOIDmode, sub, + gen_rtx (NOT, mode, source))); + source = sub; + if (subtargets) + sub = gen_reg_rtx (mode); + emit_insn (gen_rtx (SET, VOIDmode, sub, + gen_rtx (AND, mode, source, GEN_INT (temp1)))); + emit_insn (gen_rtx (SET, VOIDmode, target, + gen_rtx (NOT, mode, sub))); + return 3; + } + break; + + case AND: + /* See if two shifts will do 2 or more insn's worth of work. */ + if (clear_sign_bit_copies >= 16 && clear_sign_bit_copies < 24) + { + HOST_WIDE_INT shift_mask = ((0xffffffff + << (32 - clear_sign_bit_copies)) + & 0xffffffff); + rtx new_source; + rtx shift = GEN_INT (clear_sign_bit_copies); + + if ((remainder | shift_mask) != 0xffffffff) + { + new_source = subtargets ? gen_reg_rtx (mode) : target; + insns = arm_split_constant (AND, mode, remainder | shift_mask, + new_source, source, subtargets); + source = new_source; + } + + new_source = subtargets ? gen_reg_rtx (mode) : target; + emit_insn (gen_ashlsi3 (new_source, source, shift)); + emit_insn (gen_lshrsi3 (target, new_source, shift)); + return insns + 2; + } + + if (clear_zero_bit_copies >= 16 && clear_zero_bit_copies < 24) + { + HOST_WIDE_INT shift_mask = (1 << clear_zero_bit_copies) - 1; + rtx new_source; + rtx shift = GEN_INT (clear_zero_bit_copies); + + if ((remainder | shift_mask) != 0xffffffff) + { + new_source = subtargets ? gen_reg_rtx (mode) : target; + insns = arm_split_constant (AND, mode, remainder | shift_mask, + new_source, source, subtargets); + source = new_source; + } + + new_source = subtargets ? gen_reg_rtx (mode) : target; + emit_insn (gen_lshrsi3 (new_source, source, shift)); + emit_insn (gen_ashlsi3 (target, new_source, shift)); + return insns + 2; + } + + break; + + default: + break; + } + + for (i = 0; i < 32; i++) + if (remainder & (1 << i)) + num_bits_set++; + + if (code == AND || (can_invert && num_bits_set > 16)) + remainder = (~remainder) & 0xffffffff; + else if (code == PLUS && num_bits_set > 16) + remainder = (-remainder) & 0xffffffff; + else + { + can_invert = 0; + can_negate = 0; + } + + /* Now try and find a way of doing the job in either two or three + instructions. + We start by looking for the largest block of zeros that are aligned on + a 2-bit boundary, we then fill up the temps, wrapping around to the + top of the word when we drop off the bottom. + In the worst case this code should produce no more than four insns. */ + { + int best_start = 0; + int best_consecutive_zeros = 0; + + for (i = 0; i < 32; i += 2) + { + int consecutive_zeros = 0; + + if (! (remainder & (3 << i))) + { + while ((i < 32) && ! (remainder & (3 << i))) + { + consecutive_zeros += 2; + i += 2; + } + if (consecutive_zeros > best_consecutive_zeros) + { + best_consecutive_zeros = consecutive_zeros; + best_start = i - consecutive_zeros; + } + i -= 2; + } + } + + /* Now start emitting the insns, starting with the one with the highest + bit set: we do this so that the smallest number will be emitted last; + this is more likely to be combinable with addressing insns. */ + i = best_start; + do + { + int end; + + if (i <= 0) + i += 32; + if (remainder & (3 << (i - 2))) + { + end = i - 8; + if (end < 0) + end += 32; + temp1 = remainder & ((0x0ff << end) + | ((i < end) ? (0xff >> (32 - end)) : 0)); + remainder &= ~temp1; + + if (code == SET) + { + emit_insn (gen_rtx (SET, VOIDmode, + new_src = (subtargets ? gen_reg_rtx (mode) + : target), + GEN_INT (can_invert ? ~temp1 : temp1))); + can_invert = 0; + code = PLUS; + } + else if (code == MINUS) + { + emit_insn (gen_rtx (SET, VOIDmode, + new_src = (subtargets ? gen_reg_rtx (mode) + : target), + gen_rtx (code, mode, GEN_INT (temp1), + source))); + code = PLUS; + } + else + { + emit_insn (gen_rtx (SET, VOIDmode, + new_src = remainder ? (subtargets + ? gen_reg_rtx (mode) + : target) : target, + gen_rtx (code, mode, source, + GEN_INT (can_invert ? ~temp1 + : (can_negate + ? -temp1 : temp1))))); + } + + insns++; + source = new_src; + i -= 6; + } + i -= 2; + } while (remainder); + } + return insns; +} + +#define REG_OR_SUBREG_REG(X) \ + (GET_CODE (X) == REG \ + || (GET_CODE (X) == SUBREG && GET_CODE (SUBREG_REG (X)) == REG)) + +#define REG_OR_SUBREG_RTX(X) \ + (GET_CODE (X) == REG ? (X) : SUBREG_REG (X)) + +#define ARM_FRAME_RTX(X) \ + ((X) == frame_pointer_rtx || (X) == stack_pointer_rtx \ + || (X) == arg_pointer_rtx) + +int +arm_rtx_costs (x, code, outer_code) + rtx x; + enum rtx_code code, outer_code; +{ + enum machine_mode mode = GET_MODE (x); + enum rtx_code subcode; + int extra_cost; + + switch (code) + { + case MEM: + /* Memory costs quite a lot for the first word, but subsequent words + load at the equivalent of a single insn each. */ + return (10 + 4 * ((GET_MODE_SIZE (mode) - 1) / UNITS_PER_WORD) + + (CONSTANT_POOL_ADDRESS_P (x) ? 4 : 0)); + + case DIV: + case MOD: + return 100; + + case ROTATE: + if (mode == SImode && GET_CODE (XEXP (x, 1)) == REG) + return 4; + /* Fall through */ + case ROTATERT: + if (mode != SImode) + return 8; + /* Fall through */ + case ASHIFT: case LSHIFTRT: case ASHIFTRT: + if (mode == DImode) + return (8 + (GET_CODE (XEXP (x, 1)) == CONST_INT ? 0 : 8) + + ((GET_CODE (XEXP (x, 0)) == REG + || (GET_CODE (XEXP (x, 0)) == SUBREG + && GET_CODE (SUBREG_REG (XEXP (x, 0))) == REG)) + ? 0 : 8)); + return (1 + ((GET_CODE (XEXP (x, 0)) == REG + || (GET_CODE (XEXP (x, 0)) == SUBREG + && GET_CODE (SUBREG_REG (XEXP (x, 0))) == REG)) + ? 0 : 4) + + ((GET_CODE (XEXP (x, 1)) == REG + || (GET_CODE (XEXP (x, 1)) == SUBREG + && GET_CODE (SUBREG_REG (XEXP (x, 1))) == REG) + || (GET_CODE (XEXP (x, 1)) == CONST_INT)) + ? 0 : 4)); + + case MINUS: + if (mode == DImode) + return (4 + (REG_OR_SUBREG_REG (XEXP (x, 1)) ? 0 : 8) + + ((REG_OR_SUBREG_REG (XEXP (x, 0)) + || (GET_CODE (XEXP (x, 0)) == CONST_INT + && const_ok_for_arm (INTVAL (XEXP (x, 0))))) + ? 0 : 8)); + + if (GET_MODE_CLASS (mode) == MODE_FLOAT) + return (2 + ((REG_OR_SUBREG_REG (XEXP (x, 1)) + || (GET_CODE (XEXP (x, 1)) == CONST_DOUBLE + && const_double_rtx_ok_for_fpu (XEXP (x, 1)))) + ? 0 : 8) + + ((REG_OR_SUBREG_REG (XEXP (x, 0)) + || (GET_CODE (XEXP (x, 0)) == CONST_DOUBLE + && const_double_rtx_ok_for_fpu (XEXP (x, 0)))) + ? 0 : 8)); + + if (((GET_CODE (XEXP (x, 0)) == CONST_INT + && const_ok_for_arm (INTVAL (XEXP (x, 0))) + && REG_OR_SUBREG_REG (XEXP (x, 1)))) + || (((subcode = GET_CODE (XEXP (x, 1))) == ASHIFT + || subcode == ASHIFTRT || subcode == LSHIFTRT + || subcode == ROTATE || subcode == ROTATERT + || (subcode == MULT + && GET_CODE (XEXP (XEXP (x, 1), 1)) == CONST_INT + && ((INTVAL (XEXP (XEXP (x, 1), 1)) & + (INTVAL (XEXP (XEXP (x, 1), 1)) - 1)) == 0))) + && REG_OR_SUBREG_REG (XEXP (XEXP (x, 1), 0)) + && (REG_OR_SUBREG_REG (XEXP (XEXP (x, 1), 1)) + || GET_CODE (XEXP (XEXP (x, 1), 1)) == CONST_INT) + && REG_OR_SUBREG_REG (XEXP (x, 0)))) + return 1; + /* Fall through */ + + case PLUS: + if (GET_MODE_CLASS (mode) == MODE_FLOAT) + return (2 + (REG_OR_SUBREG_REG (XEXP (x, 0)) ? 0 : 8) + + ((REG_OR_SUBREG_REG (XEXP (x, 1)) + || (GET_CODE (XEXP (x, 1)) == CONST_DOUBLE + && const_double_rtx_ok_for_fpu (XEXP (x, 1)))) + ? 0 : 8)); + + /* Fall through */ + case AND: case XOR: case IOR: + extra_cost = 0; + + /* Normally the frame registers will be spilt into reg+const during + reload, so it is a bad idea to combine them with other instructions, + since then they might not be moved outside of loops. As a compromise + we allow integration with ops that have a constant as their second + operand. */ + if ((REG_OR_SUBREG_REG (XEXP (x, 0)) + && ARM_FRAME_RTX (REG_OR_SUBREG_RTX (XEXP (x, 0))) + && GET_CODE (XEXP (x, 1)) != CONST_INT) + || (REG_OR_SUBREG_REG (XEXP (x, 0)) + && ARM_FRAME_RTX (REG_OR_SUBREG_RTX (XEXP (x, 0))))) + extra_cost = 4; + + if (mode == DImode) + return (4 + extra_cost + (REG_OR_SUBREG_REG (XEXP (x, 0)) ? 0 : 8) + + ((REG_OR_SUBREG_REG (XEXP (x, 1)) + || (GET_CODE (XEXP (x, 1)) == CONST_INT + && const_ok_for_op (INTVAL (XEXP (x, 1)), code, mode))) + ? 0 : 8)); + + if (REG_OR_SUBREG_REG (XEXP (x, 0))) + return (1 + (GET_CODE (XEXP (x, 1)) == CONST_INT ? 0 : extra_cost) + + ((REG_OR_SUBREG_REG (XEXP (x, 1)) + || (GET_CODE (XEXP (x, 1)) == CONST_INT + && const_ok_for_op (INTVAL (XEXP (x, 1)), code, mode))) + ? 0 : 4)); + + else if (REG_OR_SUBREG_REG (XEXP (x, 1))) + return (1 + extra_cost + + ((((subcode = GET_CODE (XEXP (x, 0))) == ASHIFT + || subcode == LSHIFTRT || subcode == ASHIFTRT + || subcode == ROTATE || subcode == ROTATERT + || (subcode == MULT + && GET_CODE (XEXP (XEXP (x, 0), 1)) == CONST_INT + && ((INTVAL (XEXP (XEXP (x, 0), 1)) & + (INTVAL (XEXP (XEXP (x, 0), 1)) - 1)) == 0)) + && (REG_OR_SUBREG_REG (XEXP (XEXP (x, 0), 0))) + && ((REG_OR_SUBREG_REG (XEXP (XEXP (x, 0), 1))) + || GET_CODE (XEXP (XEXP (x, 0), 1)) == CONST_INT))) + ? 0 : 4)); + + return 8; + + case MULT: + if (GET_MODE_CLASS (mode) == MODE_FLOAT + || mode == DImode) + return 30; + + if (GET_CODE (XEXP (x, 1)) == CONST_INT) + { + HOST_WIDE_INT i = INTVAL (XEXP (x, 1)) & 0xffffffff; + int add_cost = const_ok_for_arm (i) ? 4 : 8; + int j; + + /* This will need adjusting for ARM's with fast multiplies */ + for (j = 0; i && j < 32; j += 2) + { + i &= ~(3 << j); + add_cost += 2; + } + + return add_cost; + } + + return (30 + (REG_OR_SUBREG_REG (XEXP (x, 0)) ? 0 : 4) + + (REG_OR_SUBREG_REG (XEXP (x, 1)) ? 0 : 4)); + + case NEG: + if (GET_MODE_CLASS (mode) == MODE_FLOAT) + return 4 + (REG_OR_SUBREG_REG (XEXP (x, 0)) ? 0 : 6); + /* Fall through */ + case NOT: + if (mode == DImode) + return 4 + (REG_OR_SUBREG_REG (XEXP (x, 0)) ? 0 : 4); + + return 1 + (REG_OR_SUBREG_REG (XEXP (x, 0)) ? 0 : 4); + + case IF_THEN_ELSE: + if (GET_CODE (XEXP (x, 1)) == PC || GET_CODE (XEXP (x, 2)) == PC) + return 14; + return 2; + + case COMPARE: + return 1; + + case ABS: + return 4 + (mode == DImode ? 4 : 0); + + case SIGN_EXTEND: + if (GET_MODE (XEXP (x, 0)) == QImode) + return (4 + (mode == DImode ? 4 : 0) + + (GET_CODE (XEXP (x, 0)) == MEM ? 10 : 0)); + /* Fall through */ + case ZERO_EXTEND: + switch (GET_MODE (XEXP (x, 0))) + { + case QImode: + return (1 + (mode == DImode ? 4 : 0) + + (GET_CODE (XEXP (x, 0)) == MEM ? 10 : 0)); + + case HImode: + return (4 + (mode == DImode ? 4 : 0) + + (GET_CODE (XEXP (x, 0)) == MEM ? 10 : 0)); + + case SImode: + return (1 + (GET_CODE (XEXP (x, 0)) == MEM ? 10 : 0)); + } + abort (); + + default: + return 99; + } +} + +/* This code has been fixed for cross compilation. */ + +static int fpa_consts_inited = 0; + +char *strings_fpa[8] = { + "0.0", + "1.0", + "2.0", + "3.0", + "4.0", + "5.0", + "0.5", + "10.0" + }; + +static REAL_VALUE_TYPE values_fpa[8]; + +static void +init_fpa_table () +{ + int i; + REAL_VALUE_TYPE r; + + for (i = 0; i < 8; i++) + { + r = REAL_VALUE_ATOF (strings_fpa[i], DFmode); + values_fpa[i] = r; + } + + fpa_consts_inited = 1; +} /* Return TRUE if rtx X is a valid immediate FPU constant. */ @@ -116,18 +921,108 @@ 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 */ + REAL_VALUE_TYPE r; + int i; + + if (!fpa_consts_inited) + init_fpa_table (); + + REAL_VALUE_FROM_CONST_DOUBLE (r, x); + if (REAL_VALUE_MINUS_ZERO (r)) + return 0; + + for (i = 0; i < 8; i++) + if (REAL_VALUES_EQUAL (r, values_fpa[i])) + return 1; + + return 0; +} + +/* Return TRUE if rtx X is a valid immediate FPU constant. */ + +int +neg_const_double_rtx_ok_for_fpu (x) + rtx x; +{ + REAL_VALUE_TYPE r; + int i; + + if (!fpa_consts_inited) + init_fpa_table (); + + REAL_VALUE_FROM_CONST_DOUBLE (r, x); + r = REAL_VALUE_NEGATE (r); + if (REAL_VALUE_MINUS_ZERO (r)) + return 0; + + for (i = 0; i < 8; i++) + if (REAL_VALUES_EQUAL (r, values_fpa[i])) + return 1; + + return 0; +} /* Predicates for `match_operand' and `match_operator'. */ +/* s_register_operand is the same as register_operand, but it doesn't accept + (SUBREG (MEM)...). */ + +int +s_register_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (GET_MODE (op) != mode && mode != VOIDmode) + return 0; + + if (GET_CODE (op) == SUBREG) + op = SUBREG_REG (op); + + /* We don't consider registers whose class is NO_REGS + to be a register operand. */ + return (GET_CODE (op) == REG + && (REGNO (op) >= FIRST_PSEUDO_REGISTER + || REGNO_REG_CLASS (REGNO (op)) != NO_REGS)); +} + +/* Only accept reg, subreg(reg), const_int. */ + +int +reg_or_int_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (GET_CODE (op) == CONST_INT) + return 1; + + if (GET_MODE (op) != mode && mode != VOIDmode) + return 0; + + if (GET_CODE (op) == SUBREG) + op = SUBREG_REG (op); + + /* We don't consider registers whose class is NO_REGS + to be a register operand. */ + return (GET_CODE (op) == REG + && (REGNO (op) >= FIRST_PSEUDO_REGISTER + || REGNO_REG_CLASS (REGNO (op)) != NO_REGS)); +} + +/* 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))); +} + /* Return TRUE for valid operands for the rhs of an ARM instruction. */ int @@ -135,9 +1030,47 @@ arm_rhs_operand (op, mode) rtx op; enum machine_mode mode; { - return (register_operand (op, mode) + return (s_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 ARM instruction, or a load. + */ + +int +arm_rhsm_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return (s_register_operand (op, mode) + || (GET_CODE (op) == CONST_INT && const_ok_for_arm (INTVAL (op))) + || memory_operand (op, mode)); +} + +/* Return TRUE for valid operands for the rhs of an ARM instruction, or if a + constant that is valid when negated. */ + +int +arm_add_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return (s_register_operand (op, mode) + || (GET_CODE (op) == CONST_INT + && (const_ok_for_arm (INTVAL (op)) + || const_ok_for_arm (-INTVAL (op))))); +} + +int +arm_not_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return (s_register_operand (op, mode) + || (GET_CODE (op) == CONST_INT + && (const_ok_for_arm (INTVAL (op)) + || const_ok_for_arm (~INTVAL (op))))); +} /* Return TRUE for valid operands for the rhs of an FPU instruction. */ @@ -146,12 +1079,27 @@ fpu_rhs_operand (op, mode) rtx op; enum machine_mode mode; { - if (register_operand (op, mode)) - return(TRUE); + if (s_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 FALSE; +} + +int +fpu_add_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + if (s_register_operand (op, mode)) + return TRUE; + else if (GET_CODE (op) == CONST_DOUBLE) + return (const_double_rtx_ok_for_fpu (op) + || neg_const_double_rtx_ok_for_fpu (op)); + + return FALSE; +} /* Return nonzero if OP is a constant power of two. */ @@ -162,36 +1110,38 @@ power_of_two_operand (op, mode) { if (GET_CODE (op) == CONST_INT) { - int value = INTVAL(op); - return (value != 0 && (value & (value-1)) == 0); + HOST_WIDE_INT value = INTVAL(op); + return value != 0 && (value & (value - 1)) == 0; } - return (FALSE); -} /* power_of_two_operand */ + return FALSE; +} /* Return TRUE for a valid operand of a DImode operation. - Either: REG, CONST_DOUBLE or MEM(offsettable). - Note that this disallows MEM(REG+REG). */ + Either: REG, CONST_DOUBLE or MEM(DImode_address). + Note that this disallows MEM(REG+REG), but allows + MEM(PRE/POST_INC/DEC(REG)). */ int di_operand (op, mode) rtx op; enum machine_mode mode; { - if (register_operand (op, mode)) - return (TRUE); + if (s_register_operand (op, mode)) + return TRUE; switch (GET_CODE (op)) { case CONST_DOUBLE: case CONST_INT: - return (TRUE); + return TRUE; + case MEM: - return (memory_address_p (DImode, XEXP (op, 0)) - && offsettable_address_p (FALSE, DImode, XEXP (op, 0))); + return memory_address_p (DImode, XEXP (op, 0)); + default: - return (FALSE); + return FALSE; } -} /* di_operand */ +} /* Return TRUE for valid index operands. */ @@ -200,9 +1150,24 @@ 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 (s_register_operand(op, mode) + || (immediate_operand (op, mode) + && INTVAL (op) < 4096 && INTVAL (op) > -4096)); +} + +/* Return TRUE for valid shifts by a constant. This also accepts any + power of two on the (somewhat overly relaxed) assumption that the + shift operator in this case was a mult. */ + +int +const_shift_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return (power_of_two_operand (op, mode) + || (immediate_operand (op, mode) + && (INTVAL (op) < 32 && INTVAL (op) > 0))); +} /* Return TRUE for arithmetic operators which can be combined with a multiply (shift). */ @@ -221,7 +1186,7 @@ shiftable_operator (x, mode) return (code == PLUS || code == MINUS || code == IOR || code == XOR || code == AND); } -} /* shiftable_operator */ +} /* Return TRUE for shift operators. */ @@ -236,13 +1201,646 @@ shift_operator (x, mode) { enum rtx_code code = GET_CODE (x); - return (code == ASHIFT || code == LSHIFT - || code == ASHIFTRT || code == LSHIFTRT); + if (code == MULT) + return power_of_two_operand (XEXP (x, 1)); + + return (code == ASHIFT || code == ASHIFTRT || code == LSHIFTRT + || code == ROTATERT); + } +} + +int equality_operator (x, mode) + rtx x; + enum machine_mode mode; +{ + return GET_CODE (x) == EQ || GET_CODE (x) == NE; +} + +/* Return TRUE for SMIN SMAX UMIN UMAX operators. */ + +int +minmax_operator (x, mode) + rtx x; + enum machine_mode mode; +{ + enum rtx_code code = GET_CODE (x); + + if (GET_MODE (x) != mode) + return FALSE; + + return code == SMIN || code == SMAX || code == UMIN || code == UMAX; +} + +/* return TRUE if x is EQ or NE */ + +/* Return TRUE if this is the condition code register, if we aren't given + a mode, accept any class CCmode register */ + +int +cc_register (x, mode) + rtx x; + enum machine_mode mode; +{ + if (mode == VOIDmode) + { + mode = GET_MODE (x); + if (GET_MODE_CLASS (mode) != MODE_CC) + return FALSE; + } + + if (mode == GET_MODE (x) && GET_CODE (x) == REG && REGNO (x) == 24) + return TRUE; + + return FALSE; +} + +/* Return TRUE if this is the condition code register, if we aren't given + a mode, accept any mode in class CC_MODE that is reversible */ + +int +reversible_cc_register (x, mode) + rtx x; + enum machine_mode mode; +{ + if (mode == VOIDmode) + { + mode = GET_MODE (x); + if (GET_MODE_CLASS (mode) != MODE_CC + && GET_CODE (x) == REG && REGNO (x) == 24) + abort (); + if (GET_MODE_CLASS (mode) != MODE_CC + || (! flag_fast_math && ! REVERSIBLE_CC_MODE (mode))) + return FALSE; + } + + if (mode == GET_MODE (x) && GET_CODE (x) == REG && REGNO (x) == 24) + return TRUE; + + return FALSE; +} + +enum rtx_code +minmax_code (x) + rtx x; +{ + enum rtx_code code = GET_CODE (x); + + if (code == SMAX) + return GE; + else if (code == SMIN) + return LE; + else if (code == UMIN) + return LEU; + else if (code == UMAX) + return GEU; + + abort (); +} + +/* Return 1 if memory locations are adjacent */ + +int +adjacent_mem_locations (a, b) + rtx a, b; +{ + int val0 = 0, val1 = 0; + int reg0, reg1; + + if ((GET_CODE (XEXP (a, 0)) == REG + || (GET_CODE (XEXP (a, 0)) == PLUS + && GET_CODE (XEXP (XEXP (a, 0), 1)) == CONST_INT)) + && (GET_CODE (XEXP (b, 0)) == REG + || (GET_CODE (XEXP (b, 0)) == PLUS + && GET_CODE (XEXP (XEXP (b, 0), 1)) == CONST_INT))) + { + if (GET_CODE (XEXP (a, 0)) == PLUS) + { + reg0 = REGNO (XEXP (XEXP (a, 0), 0)); + val0 = INTVAL (XEXP (XEXP (a, 0), 1)); + } + else + reg0 = REGNO (XEXP (a, 0)); + if (GET_CODE (XEXP (b, 0)) == PLUS) + { + reg1 = REGNO (XEXP (XEXP (b, 0), 0)); + val1 = INTVAL (XEXP (XEXP (b, 0), 1)); + } + else + reg1 = REGNO (XEXP (b, 0)); + return (reg0 == reg1) && ((val1 - val0) == 4 || (val0 - val1) == 4); + } + return 0; +} + +/* Return 1 if OP is a load multiple operation. It is known to be + parallel and the first section will be tested. */ + +int +load_multiple_operation (op, mode) + rtx op; + enum machine_mode mode; +{ + HOST_WIDE_INT count = XVECLEN (op, 0); + int dest_regno; + rtx src_addr; + HOST_WIDE_INT i = 1, base = 0; + rtx elt; + + if (count <= 1 + || GET_CODE (XVECEXP (op, 0, 0)) != SET) + return 0; + + /* Check to see if this might be a write-back */ + if (GET_CODE (SET_SRC (elt = XVECEXP (op, 0, 0))) == PLUS) + { + i++; + base = 1; + + /* Now check it more carefully */ + if (GET_CODE (SET_DEST (elt)) != REG + || GET_CODE (XEXP (SET_SRC (elt), 0)) != REG + || REGNO (XEXP (SET_SRC (elt), 0)) != REGNO (SET_DEST (elt)) + || GET_CODE (XEXP (SET_SRC (elt), 1)) != CONST_INT + || INTVAL (XEXP (SET_SRC (elt), 1)) != (count - 2) * 4 + || GET_CODE (XVECEXP (op, 0, count - 1)) != CLOBBER + || GET_CODE (XEXP (XVECEXP (op, 0, count - 1), 0)) != REG + || REGNO (XEXP (XVECEXP (op, 0, count - 1), 0)) + != REGNO (SET_DEST (elt))) + return 0; + + count--; + } + + /* Perform a quick check so we don't blow up below. */ + if (count <= i + || GET_CODE (XVECEXP (op, 0, i - 1)) != SET + || GET_CODE (SET_DEST (XVECEXP (op, 0, i - 1))) != REG + || GET_CODE (SET_SRC (XVECEXP (op, 0, i - 1))) != MEM) + return 0; + + dest_regno = REGNO (SET_DEST (XVECEXP (op, 0, i - 1))); + src_addr = XEXP (SET_SRC (XVECEXP (op, 0, i - 1)), 0); + + for (; i < count; i++) + { + rtx elt = XVECEXP (op, 0, i); + + if (GET_CODE (elt) != SET + || GET_CODE (SET_DEST (elt)) != REG + || GET_MODE (SET_DEST (elt)) != SImode + || REGNO (SET_DEST (elt)) != dest_regno + i - base + || 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 - base) * 4) + return 0; + } + + return 1; +} + +/* Return 1 if OP is a store multiple operation. It is known to be + parallel and the first section will be tested. */ + +int +store_multiple_operation (op, mode) + rtx op; + enum machine_mode mode; +{ + HOST_WIDE_INT count = XVECLEN (op, 0); + int src_regno; + rtx dest_addr; + HOST_WIDE_INT i = 1, base = 0; + rtx elt; + + if (count <= 1 + || GET_CODE (XVECEXP (op, 0, 0)) != SET) + return 0; + + /* Check to see if this might be a write-back */ + if (GET_CODE (SET_SRC (elt = XVECEXP (op, 0, 0))) == PLUS) + { + i++; + base = 1; + + /* Now check it more carefully */ + if (GET_CODE (SET_DEST (elt)) != REG + || GET_CODE (XEXP (SET_SRC (elt), 0)) != REG + || REGNO (XEXP (SET_SRC (elt), 0)) != REGNO (SET_DEST (elt)) + || GET_CODE (XEXP (SET_SRC (elt), 1)) != CONST_INT + || INTVAL (XEXP (SET_SRC (elt), 1)) != (count - 2) * 4 + || GET_CODE (XVECEXP (op, 0, count - 1)) != CLOBBER + || GET_CODE (XEXP (XVECEXP (op, 0, count - 1), 0)) != REG + || REGNO (XEXP (XVECEXP (op, 0, count - 1), 0)) + != REGNO (SET_DEST (elt))) + return 0; + + count--; + } + + /* Perform a quick check so we don't blow up below. */ + if (count <= i + || GET_CODE (XVECEXP (op, 0, i - 1)) != SET + || GET_CODE (SET_DEST (XVECEXP (op, 0, i - 1))) != MEM + || GET_CODE (SET_SRC (XVECEXP (op, 0, i - 1))) != REG) + return 0; + + src_regno = REGNO (SET_SRC (XVECEXP (op, 0, i - 1))); + dest_addr = XEXP (SET_DEST (XVECEXP (op, 0, i - 1)), 0); + + for (; i < count; i++) + { + elt = XVECEXP (op, 0, i); + + if (GET_CODE (elt) != SET + || GET_CODE (SET_SRC (elt)) != REG + || GET_MODE (SET_SRC (elt)) != SImode + || REGNO (SET_SRC (elt)) != src_regno + i - base + || 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 - base) * 4) + return 0; + } + + return 1; +} + +int +multi_register_push (op, mode) + rtx op; + enum machine_mode mode; +{ + if (GET_CODE (op) != PARALLEL + || (GET_CODE (XVECEXP (op, 0, 0)) != SET) + || (GET_CODE (SET_SRC (XVECEXP (op, 0, 0))) != UNSPEC) + || (XINT (SET_SRC (XVECEXP (op, 0, 0)), 1) != 2)) + return 0; + + return 1; +} + + +/* Routines for use with attributes */ + +int +const_pool_offset (symbol) + rtx symbol; +{ + return get_pool_offset (symbol) - get_pool_size () - get_prologue_size (); +} + +/* Routines for use in generating RTL */ + +rtx +arm_gen_load_multiple (base_regno, count, from, up, write_back) + int base_regno; + int count; + rtx from; + int up; + int write_back; +{ + int i = 0, j; + rtx result; + int sign = up ? 1 : -1; + + result = gen_rtx (PARALLEL, VOIDmode, + rtvec_alloc (count + (write_back ? 2 : 0))); + if (write_back) + { + XVECEXP (result, 0, 0) + = gen_rtx (SET, GET_MODE (from), from, + plus_constant (from, count * 4 * sign)); + i = 1; + count++; + } + + for (j = 0; i < count; i++, j++) + { + XVECEXP (result, 0, i) + = gen_rtx (SET, VOIDmode, gen_rtx (REG, SImode, base_regno + j), + gen_rtx (MEM, SImode, + plus_constant (from, j * 4 * sign))); + } + + if (write_back) + XVECEXP (result, 0, i) = gen_rtx (CLOBBER, SImode, from); + + return result; +} + +rtx +arm_gen_store_multiple (base_regno, count, to, up, write_back) + int base_regno; + int count; + rtx to; + int up; + int write_back; +{ + int i = 0, j; + rtx result; + int sign = up ? 1 : -1; + + result = gen_rtx (PARALLEL, VOIDmode, + rtvec_alloc (count + (write_back ? 2 : 0))); + if (write_back) + { + XVECEXP (result, 0, 0) + = gen_rtx (SET, GET_MODE (to), to, + plus_constant (to, count * 4 * sign)); + i = 1; + count++; } -} /* shift_operator */ + + for (j = 0; i < count; i++, j++) + { + XVECEXP (result, 0, i) + = gen_rtx (SET, VOIDmode, + gen_rtx (MEM, SImode, plus_constant (to, j * 4 * sign)), + gen_rtx (REG, SImode, base_regno + j)); + } + + if (write_back) + XVECEXP (result, 0, i) = gen_rtx (CLOBBER, SImode, to); + + return result; +} + +int +arm_gen_movstrqi (operands) + rtx *operands; +{ + HOST_WIDE_INT in_words_to_go, out_words_to_go, last_bytes; + int i, r; + rtx const_sxteen = gen_rtx (CONST_INT, SImode, 16); + rtx src, dst; + rtx st_src, st_dst, end_src, end_dst, fin_src, fin_dst; + rtx part_bytes_reg = NULL; + extern int optimize; + + if (GET_CODE (operands[2]) != CONST_INT + || GET_CODE (operands[3]) != CONST_INT + || INTVAL (operands[2]) > 64 + || INTVAL (operands[3]) & 3) + return 0; + + st_dst = XEXP (operands[0], 0); + st_src = XEXP (operands[1], 0); + fin_dst = dst = copy_to_mode_reg (SImode, st_dst); + fin_src = src = copy_to_mode_reg (SImode, st_src); + + in_words_to_go = (INTVAL (operands[2]) + 3) / 4; + out_words_to_go = INTVAL (operands[2]) / 4; + last_bytes = INTVAL (operands[2]) & 3; + + if (out_words_to_go != in_words_to_go && ((in_words_to_go - 1) & 3) != 0) + part_bytes_reg = gen_rtx (REG, SImode, (in_words_to_go - 1) & 3); + + for (i = 0; in_words_to_go >= 2; i+=4) + { + emit_insn (arm_gen_load_multiple (0, (in_words_to_go > 4 + ? 4 : in_words_to_go), + src, TRUE, TRUE)); + if (out_words_to_go) + { + if (out_words_to_go != 1) + emit_insn (arm_gen_store_multiple (0, (out_words_to_go > 4 + ? 4 : out_words_to_go), + dst, TRUE, TRUE)); + else + { + emit_move_insn (gen_rtx (MEM, SImode, dst), + gen_rtx (REG, SImode, 0)); + emit_insn (gen_addsi3 (dst, dst, GEN_INT (4))); + } + } + + in_words_to_go -= in_words_to_go < 4 ? in_words_to_go : 4; + out_words_to_go -= out_words_to_go < 4 ? out_words_to_go : 4; + } + + /* OUT_WORDS_TO_GO will be zero here if there are byte stores to do. */ + if (out_words_to_go) + { + rtx sreg; + + emit_move_insn (sreg = gen_reg_rtx (SImode), gen_rtx (MEM, SImode, src)); + emit_move_insn (fin_src = gen_reg_rtx (SImode), plus_constant (src, 4)); + emit_move_insn (gen_rtx (MEM, SImode, dst), sreg); + emit_move_insn (fin_dst = gen_reg_rtx (SImode), plus_constant (dst, 4)); + in_words_to_go--; + + if (in_words_to_go) /* Sanity check */ + abort (); + } + + if (in_words_to_go) + { + if (in_words_to_go < 0) + abort (); + + part_bytes_reg = copy_to_mode_reg (SImode, gen_rtx (MEM, SImode, src)); + emit_insn (gen_addsi3 (src, src, GEN_INT (4))); + } + + if (BYTES_BIG_ENDIAN && last_bytes) + { + rtx tmp = gen_reg_rtx (SImode); + + if (part_bytes_reg == NULL) + abort (); + + /* The bytes we want are in the top end of the word */ + emit_insn (gen_lshrsi3 (tmp, part_bytes_reg, + GEN_INT (8 * (4 - last_bytes)))); + part_bytes_reg = tmp; + + while (last_bytes) + { + emit_move_insn (gen_rtx (MEM, QImode, + plus_constant (dst, last_bytes - 1)), + gen_rtx (SUBREG, QImode, part_bytes_reg, 0)); + if (--last_bytes) + { + tmp = gen_reg_rtx (SImode); + emit_insn (gen_lshrsi3 (tmp, part_bytes_reg, GEN_INT (8))); + part_bytes_reg = tmp; + } + } + + } + else + { + while (last_bytes) + { + if (part_bytes_reg == NULL) + abort (); + + emit_move_insn (gen_rtx (MEM, QImode, dst), + gen_rtx (SUBREG, QImode, part_bytes_reg, 0)); + emit_insn (gen_addsi3 (dst, dst, const1_rtx)); + if (--last_bytes) + { + rtx tmp = gen_reg_rtx (SImode); + emit_insn (gen_lshrsi3 (tmp, part_bytes_reg, GEN_INT (8))); + part_bytes_reg = tmp; + } + } + } + + return 1; +} + +/* X and Y are two things to compare using CODE. Emit the compare insn and + return the rtx for register 0 in the proper mode. FP means this is a + floating point compare: I don't think that it is needed on the arm. */ + +rtx +gen_compare_reg (code, x, y, fp) + enum rtx_code code; + rtx x, y; +{ + enum machine_mode mode = SELECT_CC_MODE (code, x, y); + rtx cc_reg = gen_rtx (REG, mode, 24); + + emit_insn (gen_rtx (SET, VOIDmode, cc_reg, + gen_rtx (COMPARE, mode, x, y))); + + return cc_reg; +} + +void +arm_reload_in_hi (operands) + rtx *operands; +{ + rtx base = find_replacement (&XEXP (operands[1], 0)); + + emit_insn (gen_zero_extendqisi2 (operands[2], gen_rtx (MEM, QImode, base))); + emit_insn (gen_zero_extendqisi2 (gen_rtx (SUBREG, SImode, operands[0], 0), + gen_rtx (MEM, QImode, + plus_constant (base, 1)))); + if (BYTES_BIG_ENDIAN) + emit_insn (gen_rtx (SET, VOIDmode, gen_rtx (SUBREG, SImode, + operands[0], 0), + gen_rtx (IOR, SImode, + gen_rtx (ASHIFT, SImode, + gen_rtx (SUBREG, SImode, + operands[0], 0), + GEN_INT (8)), + operands[2]))); + else + emit_insn (gen_rtx (SET, VOIDmode, gen_rtx (SUBREG, SImode, + operands[0], 0), + gen_rtx (IOR, SImode, + gen_rtx (ASHIFT, SImode, + operands[2], + GEN_INT (8)), + gen_rtx (SUBREG, SImode, operands[0], 0)))); +} + +void +arm_reload_out_hi (operands) + rtx *operands; +{ + rtx base = find_replacement (&XEXP (operands[0], 0)); + + if (BYTES_BIG_ENDIAN) + { + emit_insn (gen_movqi (gen_rtx (MEM, QImode, plus_constant (base, 1)), + gen_rtx (SUBREG, QImode, operands[1], 0))); + emit_insn (gen_lshrsi3 (operands[2], + gen_rtx (SUBREG, SImode, operands[1], 0), + GEN_INT (8))); + emit_insn (gen_movqi (gen_rtx (MEM, QImode, base), + gen_rtx (SUBREG, QImode, operands[2], 0))); + } + else + { + emit_insn (gen_movqi (gen_rtx (MEM, QImode, base), + gen_rtx (SUBREG, QImode, operands[1], 0))); + emit_insn (gen_lshrsi3 (operands[2], + gen_rtx (SUBREG, SImode, operands[1], 0), + GEN_INT (8))); + emit_insn (gen_movqi (gen_rtx (MEM, QImode, plus_constant (base, 1)), + gen_rtx (SUBREG, QImode, operands[2], 0))); + } +} + +/* Check to see if a branch is forwards or backwards. Return TRUE if it + is backwards. */ + +int +arm_backwards_branch (from, to) + int from, to; +{ + return insn_addresses[to] <= insn_addresses[from]; +} + +/* Check to see if a branch is within the distance that can be done using + an arithmetic expression. */ +int +short_branch (from, to) + int from, to; +{ + int delta = insn_addresses[from] + 8 - insn_addresses[to]; + + return abs (delta) < 980; /* A small margin for safety */ +} + +/* Check to see that the insn isn't the target of the conditionalizing + code */ +int +arm_insn_not_targeted (insn) + rtx insn; +{ + return insn != arm_target_insn; +} + /* Routines to output assembly language. */ +/* If the rtx is the correct value then return the string of the number. + In this way we can ensure that valid double constants are generated even + when cross compiling. */ +char * +fp_immediate_constant (x) + rtx x; +{ + REAL_VALUE_TYPE r; + int i; + + if (!fpa_consts_inited) + init_fpa_table (); + + REAL_VALUE_FROM_CONST_DOUBLE (r, x); + for (i = 0; i < 8; i++) + if (REAL_VALUES_EQUAL (r, values_fpa[i])) + return strings_fpa[i]; + + abort (); +} + +/* As for fp_immediate_constant, but value is passed directly, not in rtx. */ +static char * +fp_const_from_val (r) + REAL_VALUE_TYPE *r; +{ + int i; + + if (! fpa_consts_inited) + init_fpa_table (); + + for (i = 0; i < 8; i++) + if (REAL_VALUES_EQUAL (*r, values_fpa[i])) + return strings_fpa[i]; + + abort (); +} + /* 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 @@ -257,37 +1855,170 @@ print_multi_reg (stream, instr, mask, ha int i; int not_first = FALSE; - fprintf (stream, "\t%s, {", instr); + fputc ('\t', stream); + fprintf (stream, instr, ARM_REG_PREFIX); + fputs (", {", stream); for (i = 0; i < 16; i++) if (mask & (1 << i)) { if (not_first) fprintf (stream, ", "); - fprintf (stream, "%s", reg_names[i]); + fprintf (stream, "%s%s", ARM_REG_PREFIX, reg_names[i]); not_first = TRUE; } + fprintf (stream, "}%s\n", hat ? "^" : ""); -} /* print_multi_reg */ +} /* Output a 'call' insn. */ char * output_call (operands) - rtx 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); + 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_asm_insn ("mov%?\t%|lr, %|pc", operands); + output_asm_insn ("mov%?\t%|pc, %0", operands); + return ""; +} + +static int +eliminate_lr2ip (x) + rtx *x; +{ + int something_changed = 0; + rtx x0 = *x; + int code = GET_CODE (x0); + register int i, j; + register char *fmt; + + switch (code) + { + case REG: + if (REGNO (x0) == 14) + { + *x = gen_rtx (REG, SImode, 12); + return 1; + } + return 0; + default: + /* Scan through the sub-elements and change any references there */ + fmt = GET_RTX_FORMAT (code); + for (i = GET_RTX_LENGTH (code) - 1; i >= 0; i--) + if (fmt[i] == 'e') + something_changed |= eliminate_lr2ip (&XEXP (x0, i)); + else if (fmt[i] == 'E') + for (j = 0; j < XVECLEN (x0, i); j++) + something_changed |= eliminate_lr2ip (&XVECEXP (x0, i, j)); + return something_changed; + } +} + +/* Output a 'call' insn that is a reference in memory. */ + +char * +output_call_mem (operands) + rtx *operands; +{ + operands[0] = copy_rtx (operands[0]); /* Be ultra careful */ + /* Handle calls using lr by using ip (which may be clobbered in subr anyway). + */ + if (eliminate_lr2ip (&operands[0])) + output_asm_insn ("mov%?\t%|ip, %|lr", operands); + + output_asm_insn ("mov%?\t%|lr, %|pc", operands); + output_asm_insn ("ldr%?\t%|pc, %0", operands); + return ""; +} + + +/* 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_long_double_fpu_from_arm (operands) + rtx *operands; +{ + int arm_reg0 = REGNO (operands[1]); + rtx ops[3]; + + if (arm_reg0 == 12) + abort(); + + ops[0] = gen_rtx (REG, SImode, arm_reg0); + ops[1] = gen_rtx (REG, SImode, 1 + arm_reg0); + ops[2] = gen_rtx (REG, SImode, 2 + arm_reg0); + + output_asm_insn ("stm%?fd\t%|sp!, {%0, %1, %2}", ops); + output_asm_insn ("ldf%?e\t%0, [%|sp], #12", operands); + return ""; +} + +/* 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_long_double_arm_from_fpu (operands) + rtx *operands; +{ + int arm_reg0 = REGNO (operands[0]); + rtx ops[3]; + + if (arm_reg0 == 12) + abort(); + + ops[0] = gen_rtx (REG, SImode, arm_reg0); + ops[1] = gen_rtx (REG, SImode, 1 + arm_reg0); + ops[2] = gen_rtx (REG, SImode, 2 + arm_reg0); + + output_asm_insn ("stf%?e\t%1, [%|sp, #-12]!", operands); + output_asm_insn ("ldm%?fd\t%|sp!, {%0, %1, %2}", ops); + return ""; +} + +/* Output a move from arm registers to arm registers of a long double + OPERANDS[0] is the destination. + OPERANDS[1] is the source. */ +char * +output_mov_long_double_arm_from_arm (operands) + rtx *operands; +{ + /* We have to be careful here because the two might overlap */ + int dest_start = REGNO (operands[0]); + int src_start = REGNO (operands[1]); + rtx ops[2]; + int i; + + if (dest_start < src_start) + { + for (i = 0; i < 3; i++) + { + ops[0] = gen_rtx (REG, SImode, dest_start + i); + ops[1] = gen_rtx (REG, SImode, src_start + i); + output_asm_insn ("mov%?\t%0, %1", ops); + } + } + else + { + for (i = 2; i >= 0; i--) + { + ops[0] = gen_rtx (REG, SImode, dest_start + i); + ops[1] = gen_rtx (REG, SImode, src_start + i); + output_asm_insn ("mov%?\t%0, %1", ops); + } + } + + return ""; +} + /* Output a move from arm registers to an fpu registers. OPERANDS[0] is an fpu register. @@ -295,7 +2026,7 @@ output_call (operands) char * output_mov_double_fpu_from_arm (operands) - rtx operands[]; + rtx *operands; { int arm_reg0 = REGNO (operands[1]); rtx ops[2]; @@ -304,10 +2035,10 @@ output_mov_double_fpu_from_arm (operands 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_asm_insn ("stm%?fd\t%|sp!, {%0, %1}", ops); + output_asm_insn ("ldf%?d\t%0, [%|sp], #8", operands); + return ""; +} /* Output a move from an fpu register to arm registers. OPERANDS[0] is the first registers of an arm register pair. @@ -315,19 +2046,20 @@ output_mov_double_fpu_from_arm (operands char * output_mov_double_arm_from_fpu (operands) - rtx 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_asm_insn ("stf%?d\t%1, [%|sp, #-8]!", operands); + output_asm_insn ("ldm%?fd\t%|sp!, {%0, %1}", ops); + return ""; +} /* Output a move between double words. It must be REG<-REG, REG<-CONST_DOUBLE, REG<-CONST_INT, REG<-MEM @@ -335,7 +2067,7 @@ output_mov_double_arm_from_fpu (operands char * output_move_double (operands) - rtx operands[]; + rtx *operands; { enum rtx_code code0 = GET_CODE (operands[0]); enum rtx_code code1 = GET_CODE (operands[1]); @@ -351,18 +2083,19 @@ output_move_double (operands) 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); + output_asm_insn("mov%?\t%0, %1", otherops); + 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); + output_asm_insn("mov%?\t%0, %1", operands); + output_asm_insn("mov%?\t%0, %1", otherops); } } else if (code1 == CONST_DOUBLE) @@ -371,37 +2104,57 @@ output_move_double (operands) 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); + output_mov_immediate (operands, FALSE, ""); + output_mov_immediate (otherops, FALSE, ""); } 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); + /* sign extend the intval into the high-order word */ + /* Note: output_mov_immediate may clobber operands[1], so we + put this out first */ + if (INTVAL (operands[1]) < 0) + output_asm_insn ("mvn%?\t%0, %1", otherops); + else + output_asm_insn ("mov%?\t%0, %1", otherops); + output_mov_immediate (operands, FALSE, ""); } else if (code1 == MEM) { - if (GET_CODE (XEXP (operands[1], 0)) == REG) + switch (GET_CODE (XEXP (operands[1], 0))) { + case 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 - { + output_asm_insn ("ldm%?ia\t%m1, %M0", operands); + break; + case PRE_INC: + output_asm_insn ("add%?\t%m1, %m1, #8", operands); + output_asm_insn ("ldm%?ia\t%m1, %M0", operands); + break; + case PRE_DEC: + output_asm_insn ("sub%?\t%m1, %m1, #8", operands); + output_asm_insn ("ldm%?ia\t%m1, %M0", operands); + break; + case POST_INC: + output_asm_insn ("ldm%?ia\t%m1!, %M0", operands); + break; + case POST_DEC: + output_asm_insn ("ldm%?ia\t%m1, %M0", operands); + output_asm_insn ("sub%?\t%m1, %m1, #8", operands); + break; + default: 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); + output_asm_insn ("ldr%?\t%0, %1", otherops); + 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); + output_asm_insn ("ldr%?\t%0, %1", operands); + output_asm_insn ("ldr%?\t%0, %1", otherops); } } } @@ -411,24 +2164,37 @@ output_move_double (operands) { 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 - { + switch (GET_CODE (XEXP (operands[0], 0))) + { + case REG: + output_asm_insn ("stm%?ia\t%m0, %M1", operands); + break; + case PRE_INC: + output_asm_insn ("add%?\t%m0, %m0, #8", operands); + output_asm_insn ("stm%?ia\t%m0, %M1", operands); + break; + case PRE_DEC: + output_asm_insn ("sub%?\t%m0, %m0, #8", operands); + output_asm_insn ("stm%?ia\t%m0, %M1", operands); + break; + case POST_INC: + output_asm_insn ("stm%?ia\t%m0!, %M1", operands); + break; + case POST_DEC: + output_asm_insn ("stm%?ia\t%m0, %M1", operands); + output_asm_insn ("sub%?\t%m0, %m0, #8", operands); + break; + default: 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); + output_asm_insn ("str%?\t%1, %0", operands); + output_asm_insn ("str%?\t%1, %0", otherops); } } else abort(); /* Constraints should prevent this */ - return(""); -} /* output_move_double */ + return ""; +} /* Output an arbitrary MOV reg, #n. @@ -436,23 +2202,25 @@ output_move_double (operands) char * output_mov_immediate (operands) - rtx operands[2]; + rtx *operands; { - int n = INTVAL (operands[1]); + HOST_WIDE_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)); + { + output_asm_insn ("mov%?\t%0, %1", operands); + return ""; + } /* Try to use one MVN */ - - if (const_ok_for_arm(~n)) + if (const_ok_for_arm (~n)) { - operands[1] = gen_rtx (CONST_INT, VOIDmode, ~n); - return (arm_output_asm_insn ("mvn\t%0, %1", operands)); + operands[1] = GEN_INT (~n); + output_asm_insn ("mvn%?\t%0, %1", operands); + return ""; } /* If all else fails, make it out of ORRs or BICs as appropriate. */ @@ -462,11 +2230,14 @@ output_mov_immediate (operands) 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); + 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_multi_immediate(operands, "mov%?\t%0, %1", "orr%?\t%0, %0, %1", 1, + n); + + return ""; +} /* Output an ADD r, s, #n where n may be too big for one instruction. If @@ -474,22 +2245,24 @@ output_mov_immediate (operands) char * output_add_immediate (operands) - rtx operands[3]; + rtx *operands; { - int n = INTVAL (operands[2]); + HOST_WIDE_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); + "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); + "add%?\t%0, %1, %2", "add%?\t%0, %0, %2", 2, + n); } - return(""); -} /* output_add_immediate */ + return ""; +} /* Output a multiple immediate operation. OPERANDS is the vector of operands referred to in the output patterns. @@ -500,14 +2273,19 @@ output_add_immediate (operands) char * output_multi_immediate (operands, instr1, instr2, immed_op, n) - rtx operands[]; + rtx *operands; char *instr1, *instr2; - int immed_op, n; + int immed_op; + HOST_WIDE_INT n; { +#if HOST_BITS_PER_WIDE_INT > 32 + n &= 0xffffffff; +#endif + if (n == 0) { operands[immed_op] = const0_rtx; - arm_output_asm_insn (instr1, operands); /* Quick and easy output */ + output_asm_insn (instr1, operands); /* Quick and easy output */ } else { @@ -515,21 +2293,19 @@ output_multi_immediate (operands, instr1 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); + operands[immed_op] = GEN_INT (n & (255 << i)); + output_asm_insn (instr, operands); instr = instr2; i += 6; } } } - return (""); -} /* output_multi_immediate */ + return ""; +} /* Return the appropriate ARM instruction for the operation code. @@ -540,185 +2316,127 @@ output_multi_immediate (operands, instr1 char * arithmetic_instr (op, shift_first_arg) rtx op; + int shift_first_arg; { - switch (GET_CODE(op)) + switch (GET_CODE (op)) { case PLUS: - return ("add"); + return "add"; + case MINUS: - if (shift_first_arg) - return ("rsb"); - else - return ("sub"); + return shift_first_arg ? "rsb" : "sub"; + case IOR: - return ("orr"); + return "orr"; + case XOR: - return ("eor"); + return "eor"; + case AND: - return ("and"); + return "and"; + default: - abort(); + 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. */ + On exit, *AMOUNTP will be -1 if the shift is by a register, or a constant + shift. */ -char * -shift_instr (op, shift_ptr) - enum rtx_code op; - rtx *shift_ptr; +static char * +shift_op (op, amountp) + rtx op; + HOST_WIDE_INT *amountp; { - int min_shift = 0; - int max_shift = 31; char *mnem; + enum rtx_code code = GET_CODE (op); - switch (op) + if (GET_CODE (XEXP (op, 1)) == REG || GET_CODE (XEXP (op, 1)) == SUBREG) + *amountp = -1; + else if (GET_CODE (XEXP (op, 1)) == CONST_INT) + *amountp = INTVAL (XEXP (op, 1)); + else + abort (); + + switch (code) { 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; + + case ROTATERT: + mnem = "ror"; + break; + + case MULT: + /* We never have to worry about the amount being other than a + power of 2, since this case can never be reloaded from a reg. */ + if (*amountp != -1) + *amountp = int_log2 (*amountp); + else + abort (); + return "asl"; + default: - abort(); + abort (); } - if (GET_CODE (*shift_ptr) == CONST_INT) + if (*amountp != -1) { - int shift = INTVAL (*shift_ptr); + /* This is not 100% correct, but follows from the desire to merge + multiplication by a power of 2 with the recognizer for a + shift. >=32 is not a valid shift for "asl", so we must try and + output a shift that produces the correct arithmetical result. + Using lsr #32 is idendical except for the fact that the carry bit + is not set correctly if we set the flags; but we never use the + carry bit from such an operation, so we can ignore that. */ + if (code == ROTATERT) + *amountp &= 31; /* Rotate is just modulo 32 */ + else if (*amountp != (*amountp & 31)) + { + if (code == ASHIFT) + mnem = "lsr"; + *amountp = 32; + } - 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 */ + /* Shifts of 0 are no-ops. */ + if (*amountp == 0) + return NULL; + } + + return mnem; +} /* Obtain the shift from the POWER of two. */ -int +HOST_WIDE_INT int_log2 (power) - unsigned int power; + HOST_WIDE_INT power; { - int shift = 0; + HOST_WIDE_INT shift = 0; while (((1 << shift) & power) == 0) { if (shift > 31) - abort(); + 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 */ + return shift; +} /* Output a .ascii pseudo-op, keeping track of lengths. This is because /bin/as is horribly restrictive. */ @@ -726,7 +2444,7 @@ output_shifted_move (op, operands) void output_ascii_pseudo_op (stream, p, len) FILE *stream; - char *p; + unsigned char *p; int len; { int i; @@ -752,6 +2470,7 @@ output_ascii_pseudo_op (stream, p, len) putc('\\', stream); len_so_far++; } + if (c >= ' ' && c < 0177) { putc (c, stream); @@ -762,158 +2481,385 @@ output_ascii_pseudo_op (stream, p, len) fprintf (stream, "\\%03o", c); len_so_far +=4; } + chars_so_far++; } + fputs ("\"\n", stream); arm_increase_location (chars_so_far); -} /* output_ascii_pseudo_op */ +} -void -output_prologue (f, frame_size) - FILE *f; - int frame_size; + +/* Try to determine whether a pattern really clobbers the link register. + This information is useful when peepholing, so that lr need not be pushed + if we combine a call followed by a return. + NOTE: This code does not check for side-effect expressions in a SET_SRC: + such a check should not be needed because these only update an existing + value within a register; the register must still be set elsewhere within + the function. */ + +static int +pattern_really_clobbers_lr (x) + rtx x; { + int i; + + switch (GET_CODE (x)) + { + case SET: + switch (GET_CODE (SET_DEST (x))) + { + case REG: + return REGNO (SET_DEST (x)) == 14; - int reg, live_regs_mask = 0, code_size = 0; - rtx operands[3]; + case SUBREG: + if (GET_CODE (XEXP (SET_DEST (x), 0)) == REG) + return REGNO (XEXP (SET_DEST (x), 0)) == 14; + + if (GET_CODE (XEXP (SET_DEST (x), 0)) == MEM) + return 0; + abort (); + + default: + return 0; + } + + case PARALLEL: + for (i = 0; i < XVECLEN (x, 0); i++) + if (pattern_really_clobbers_lr (XVECEXP (x, 0, i))) + return 1; + return 0; + + case CLOBBER: + switch (GET_CODE (XEXP (x, 0))) + { + case REG: + return REGNO (XEXP (x, 0)) == 14; + + case SUBREG: + if (GET_CODE (XEXP (XEXP (x, 0), 0)) == REG) + return REGNO (XEXP (XEXP (x, 0), 0)) == 14; + abort (); + + default: + return 0; + } - /* Nonzero if the `fp' (argument pointer) register is needed. */ - int fp_needed = 0; + case UNSPEC: + return 1; - /* Nonzero if we must stuff some register arguments onto the stack as if - they were passed there. */ - int store_arg_regs = 0; + default: + return 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; +static int +function_really_clobbers_lr (first) + rtx first; +{ + rtx insn, next; + + for (insn = first; insn; insn = next_nonnote_insn (insn)) + { + switch (GET_CODE (insn)) + { + case BARRIER: + case NOTE: + case CODE_LABEL: + case JUMP_INSN: /* Jump insns only change the PC (and conds) */ + case INLINE_HEADER: + break; + + case INSN: + if (pattern_really_clobbers_lr (PATTERN (insn))) + return 1; + break; + + case CALL_INSN: + /* Don't yet know how to handle those calls that are not to a + SYMBOL_REF */ + if (GET_CODE (PATTERN (insn)) != PARALLEL) + abort (); - if (current_function_anonymous_args && current_function_pretend_args_size) - store_arg_regs = 1; + switch (GET_CODE (XVECEXP (PATTERN (insn), 0, 0))) + { + case CALL: + if (GET_CODE (XEXP (XEXP (XVECEXP (PATTERN (insn), 0, 0), 0), 0)) + != SYMBOL_REF) + return 1; + break; - for (reg = 4; reg < 10; reg++) - if (regs_ever_live[reg]) - live_regs_mask |= (1 << reg); + case SET: + if (GET_CODE (XEXP (XEXP (SET_SRC (XVECEXP (PATTERN (insn), + 0, 0)), 0), 0)) + != SYMBOL_REF) + return 1; + break; - 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; + default: /* Don't recognize it, be safe */ + return 1; + } + + /* A call can be made (by peepholing) not to clobber lr iff it is + followed by a return. There may, however, be a use insn iff + we are returning the result of the call. + If we run off the end of the insn chain, then that means the + call was at the end of the function. Unfortunately we don't + have a return insn for the peephole to recognize, so we + must reject this. (Can this be fixed by adding our own insn?) */ + if ((next = next_nonnote_insn (insn)) == NULL) + return 1; + + if (GET_CODE (next) == INSN && GET_CODE (PATTERN (next)) == USE + && (GET_CODE (XVECEXP (PATTERN (insn), 0, 0)) == SET) + && (REGNO (SET_DEST (XVECEXP (PATTERN (insn), 0, 0))) + == REGNO (XEXP (PATTERN (next), 0)))) + if ((next = next_nonnote_insn (next)) == NULL) + return 1; + + if (GET_CODE (next) == JUMP_INSN + && GET_CODE (PATTERN (next)) == RETURN) + break; + return 1; + + default: + abort (); + } } - 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) + /* We have reached the end of the chain so lr was _not_ clobbered */ + return 0; +} + +char * +output_return_instruction (operand, really_return) + rtx operand; + int really_return; +{ + char instr[100]; + int reg, live_regs = 0; + int volatile_func = (optimize > 0 + && TREE_THIS_VOLATILE (current_function_decl)); + + return_used_this_function = 1; + + if (volatile_func) + { + rtx ops[2]; + /* If this function was declared non-returning, and we have found a tail + call, then we have to trust that the called function won't return. */ + if (! really_return) + return ""; + + /* Otherwise, trap an attempted return by aborting. */ + ops[0] = operand; + ops[1] = gen_rtx (SYMBOL_REF, Pmode, "abort"); + output_asm_insn ("bl%d0\t%a1", ops); + return ""; + } + + if (current_function_calls_alloca && ! really_return) + abort(); + + for (reg = 0; reg <= 10; reg++) + if (regs_ever_live[reg] && ! call_used_regs[reg]) + live_regs++; + + if (live_regs || (regs_ever_live[14] && ! lr_save_eliminated)) + live_regs++; + + if (frame_pointer_needed) + live_regs += 4; + + if (live_regs) { - if (store_arg_regs) - { - int arg_size, mask = 0; + if (lr_save_eliminated || ! regs_ever_live[14]) + live_regs++; - 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); - } + if (frame_pointer_needed) + strcpy (instr, "ldm%?%d0ea\t%|fp, {"); + else + strcpy (instr, "ldm%?%d0fd\t%|sp!, {"); + + for (reg = 0; reg <= 10; reg++) + if (regs_ever_live[reg] && ! call_used_regs[reg]) + { + strcat (instr, "%|"); + strcat (instr, reg_names[reg]); + if (--live_regs) + strcat (instr, ", "); + } + + if (frame_pointer_needed) + { + strcat (instr, "%|"); + strcat (instr, reg_names[11]); + strcat (instr, ", "); + strcat (instr, "%|"); + strcat (instr, reg_names[13]); + strcat (instr, ", "); + strcat (instr, "%|"); + strcat (instr, really_return ? reg_names[15] : reg_names[14]); + } else { - operands[0] = operands[1] = stack_pointer_rtx; - operands[2] = gen_rtx (CONST_INT, VOIDmode, - -current_function_pretend_args_size); - output_add_immediate (operands); + strcat (instr, "%|"); + strcat (instr, really_return ? reg_names[15] : reg_names[14]); } + strcat (instr, (TARGET_6 || !really_return) ? "}" : "}^"); + output_asm_insn (instr, &operand); } - - if (live_regs_mask) + else if (really_return) { - print_multi_reg (f, "stmfd\tsp!", live_regs_mask, FALSE); - code_size += 4; + strcpy (instr, + TARGET_6 ? "mov%?%d0\t%|pc, lr" : "mov%?%d0s\t%|pc, %|lr"); + output_asm_insn (instr, &operand); } - 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; - } + return ""; +} - if (fp_needed) - { - /* Make `fp' point to saved value of `pc'. */ +int +arm_volatile_func () +{ + return (optimize > 0 && TREE_THIS_VOLATILE (current_function_decl)); +} - 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); - } +/* Return the size of the prologue. It's not too bad if we slightly + over-estimate. */ + +static int +get_prologue_size () +{ + return profile_flag ? 12 : 0; +} + +/* The amount of stack adjustment that happens here, in output_return and in + output_epilogue must be exactly the same as was calculated during reload, + or things will point to the wrong place. The only time we can safely + ignore this constraint is when a function has no arguments on the stack, + no stack frame requirement and no live registers execpt for `lr'. If we + can guarantee that by making all function calls into tail calls and that + lr is not clobbered in any other way, then there is no need to push lr + onto the stack. */ + +void +output_func_prologue (f, frame_size) + FILE *f; + int frame_size; +{ + int reg, live_regs_mask = 0; + rtx operands[3]; + int volatile_func = (optimize > 0 + && TREE_THIS_VOLATILE (current_function_decl)); + + /* Nonzero if we must stuff some register arguments onto the stack as if + they were passed there. */ + int store_arg_regs = 0; + + if (arm_ccfsm_state || arm_target_insn) + abort (); /* Sanity check */ + + return_used_this_function = 0; + lr_save_eliminated = 0; + + fprintf (f, "\t%c args = %d, pretend = %d, frame = %d\n", + ARM_COMMENT_CHAR, current_function_args_size, + current_function_pretend_args_size, frame_size); + fprintf (f, "\t%c frame_needed = %d, current_function_anonymous_args = %d\n", + ARM_COMMENT_CHAR, frame_pointer_needed, + current_function_anonymous_args); + + if (volatile_func) + fprintf (f, "\t%c Volatile function.\n", ARM_COMMENT_CHAR); + + if (current_function_anonymous_args && current_function_pretend_args_size) + store_arg_regs = 1; + + for (reg = 0; reg <= 10; reg++) + if (regs_ever_live[reg] && ! call_used_regs[reg]) + live_regs_mask |= (1 << reg); if (frame_pointer_needed) + live_regs_mask |= 0xD800; + else if (regs_ever_live[14]) { - fprintf (f, "\tmov\trfp, sp\n"); - code_size += 4; + if (! current_function_args_size + && ! function_really_clobbers_lr (get_insns ())) + lr_save_eliminated = 1; + else + live_regs_mask |= 0x4000; } - if (frame_size) + if (live_regs_mask) { - operands[0] = operands[1] = stack_pointer_rtx; - operands[2] = gen_rtx (CONST_INT, VOIDmode, -frame_size); - output_add_immediate (operands); + /* if a di mode load/store multiple is used, and the base register + is r3, then r4 can become an ever live register without lr + doing so, in this case we need to push lr as well, or we + will fail to get a proper return. */ + + live_regs_mask |= 0x4000; + lr_save_eliminated = 0; + } - arm_increase_location (code_size); -} /* output_prologue */ + if (lr_save_eliminated) + fprintf (f,"\t%c I don't think this function clobbers lr\n", + ARM_COMMENT_CHAR); +} void -output_epilogue (f, frame_size) +output_func_epilogue (f, frame_size) FILE *f; int frame_size; { - int reg, live_regs_mask = 0, code_size = 0, fp_needed = 0; + int reg, live_regs_mask = 0, code_size = 0; + /* If we need this then it will always be at lesat this much */ + int floats_offset = 24; rtx operands[3]; + int volatile_func = (optimize > 0 + && TREE_THIS_VOLATILE (current_function_decl)); - 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 (use_return_insn() && return_used_this_function) + { + if (frame_size && !(frame_pointer_needed || TARGET_APCS)) + { + abort (); + } + goto epilogue_done; + } - if (fp_needed) + /* A volatile function should never return. Call abort. */ + if (volatile_func) { - live_regs_mask |= 0xA800; - if (frame_pointer_needed) - live_regs_mask |= (1 << FRAME_POINTER_REGNUM); + rtx op = gen_rtx (SYMBOL_REF, Pmode, "abort"); + output_asm_insn ("bl\t%a0", &op); + code_size = 4; + goto epilogue_done; } - else if (regs_ever_live[14]) - live_regs_mask |= 0x4000; - for (reg = 20; reg < 24; reg++) - if (regs_ever_live[reg]) + for (reg = 0; reg <= 10; reg++) + if (regs_ever_live[reg] && ! call_used_regs[reg]) { - fprintf (f, "\tldfe\t%s, [%s], #12\n", reg_names[reg], - frame_pointer_needed ? "rfp" : "sp"); - code_size += 4; + live_regs_mask |= (1 << reg); + floats_offset += 4; } - if (fp_needed) + if (frame_pointer_needed) { - print_multi_reg (f, "ldmea\tfp", live_regs_mask, TRUE); + for (reg = 23; reg > 15; reg--) + if (regs_ever_live[reg] && ! call_used_regs[reg]) + { + fprintf (f, "\tldfe\t%s%s, [%sfp, #-%d]\n", ARM_REG_PREFIX, + reg_names[reg], ARM_REG_PREFIX, floats_offset); + floats_offset += 12; + code_size += 4; + } + + live_regs_mask |= 0xA800; + print_multi_reg (f, "ldmea\t%sfp", live_regs_mask, + TARGET_6 ? FALSE : TRUE); code_size += 4; } else @@ -926,17 +2872,25 @@ output_epilogue (f, frame_size) output_add_immediate (operands); } + for (reg = 16; reg < 24; reg++) + if (regs_ever_live[reg] && ! call_used_regs[reg]) + { + fprintf (f, "\tldfe\t%s%s, [%ssp], #12\n", ARM_REG_PREFIX, + reg_names[reg], ARM_REG_PREFIX); + code_size += 4; + } if (current_function_pretend_args_size == 0 && regs_ever_live[14]) { - print_multi_reg (f, "ldmfd\tsp!", - (live_regs_mask & ~0x4000) | 0x8000, TRUE); + print_multi_reg (f, "ldmfd\t%ssp!", live_regs_mask | 0x8000, + TARGET_6 ? FALSE : TRUE); code_size += 4; } else { - if (live_regs_mask) + if (live_regs_mask || regs_ever_live[14]) { - print_multi_reg (f, "ldmfd\tsp!", live_regs_mask, FALSE); + live_regs_mask |= 0x4000; + print_multi_reg (f, "ldmfd\t%ssp!", live_regs_mask, FALSE); code_size += 4; } if (current_function_pretend_args_size) @@ -946,14 +2900,303 @@ output_epilogue (f, frame_size) current_function_pretend_args_size); output_add_immediate (operands); } - fputs ("\tmovs\tpc, lr\n", f); + fprintf (f, + TARGET_6 ? "\tmov\t%spc, %slr\n" : "\tmovs\t%spc, %slr\n", + ARM_REG_PREFIX, ARM_REG_PREFIX, f); code_size += 4; } } - arm_increase_location (code_size); + + epilogue_done: + + /* insn_addresses isn't allocated when not optimizing */ + + if (optimize > 0) + arm_increase_location (code_size + + insn_addresses[INSN_UID (get_last_insn ())] + + get_prologue_size ()); + current_function_anonymous_args = 0; -} /* output_epilogue */ +} + +static void +emit_multi_reg_push (mask) + int mask; +{ + int num_regs = 0; + int i, j; + rtx par; + + for (i = 0; i < 16; i++) + if (mask & (1 << i)) + num_regs++; + + if (num_regs == 0 || num_regs > 16) + abort (); + + par = gen_rtx (PARALLEL, VOIDmode, rtvec_alloc (num_regs)); + + for (i = 0; i < 16; i++) + { + if (mask & (1 << i)) + { + XVECEXP (par, 0, 0) + = gen_rtx (SET, VOIDmode, gen_rtx (MEM, BLKmode, + gen_rtx (PRE_DEC, BLKmode, + stack_pointer_rtx)), + gen_rtx (UNSPEC, BLKmode, + gen_rtvec (1, gen_rtx (REG, SImode, i)), + 2)); + break; + } + } + + for (j = 1, i++; j < num_regs; i++) + { + if (mask & (1 << i)) + { + XVECEXP (par, 0, j) + = gen_rtx (USE, VOIDmode, gen_rtx (REG, SImode, i)); + j++; + } + } + emit_insn (par); +} + +void +arm_expand_prologue () +{ + int reg; + rtx amount = GEN_INT (- get_frame_size ()); + rtx push_insn; + int num_regs; + int live_regs_mask = 0; + int store_arg_regs = 0; + int volatile_func = (optimize > 0 + && TREE_THIS_VOLATILE (current_function_decl)); + + if (current_function_anonymous_args && current_function_pretend_args_size) + store_arg_regs = 1; + + if (! volatile_func) + for (reg = 0; reg <= 10; reg++) + if (regs_ever_live[reg] && ! call_used_regs[reg]) + live_regs_mask |= 1 << reg; + + if (! volatile_func && regs_ever_live[14]) + live_regs_mask |= 0x4000; + + if (frame_pointer_needed) + { + live_regs_mask |= 0xD800; + emit_insn (gen_movsi (gen_rtx (REG, SImode, 12), + stack_pointer_rtx)); + } + + if (current_function_pretend_args_size) + { + if (store_arg_regs) + emit_multi_reg_push ((0xf0 >> (current_function_pretend_args_size / 4)) + & 0xf); + else + emit_insn (gen_addsi3 (stack_pointer_rtx, stack_pointer_rtx, + GEN_INT (-current_function_pretend_args_size))); + } + + if (live_regs_mask) + { + /* If we have to push any regs, then we must push lr as well, or + we won't get a propper return. */ + live_regs_mask |= 0x4000; + emit_multi_reg_push (live_regs_mask); + } + + /* For now the integer regs are still pushed in output_func_epilogue (). */ + + if (! volatile_func) + for (reg = 23; reg > 15; reg--) + if (regs_ever_live[reg] && ! call_used_regs[reg]) + emit_insn (gen_rtx (SET, VOIDmode, + gen_rtx (MEM, XFmode, + gen_rtx (PRE_DEC, XFmode, + stack_pointer_rtx)), + gen_rtx (REG, XFmode, reg))); + + if (frame_pointer_needed) + emit_insn (gen_addsi3 (hard_frame_pointer_rtx, gen_rtx (REG, SImode, 12), + (GEN_INT + (-(4 + current_function_pretend_args_size))))); + + if (amount != const0_rtx) + { + emit_insn (gen_addsi3 (stack_pointer_rtx, stack_pointer_rtx, amount)); + emit_insn (gen_rtx (CLOBBER, VOIDmode, + gen_rtx (MEM, BLKmode, stack_pointer_rtx))); + } + + /* If we are profiling, make sure no instructions are scheduled before + the call to mcount. */ + if (profile_flag || profile_block_flag) + emit_insn (gen_blockage ()); +} + +/* If CODE is 'd', then the X is a condition operand and the instruction + should only be executed if the condition is true. + if CODE is 'D', then the X is a condition operand and the instruciton + should only be executed if the condition is false: however, if the mode + of the comparison is CCFPEmode, then always execute the instruction -- we + do this because in these circumstances !GE does not necessarily imply LT; + in these cases the instruction pattern will take care to make sure that + an instruction containing %d will follow, thereby undoing the effects of + doing this instrucion unconditionally. + If CODE is 'N' then X is a floating point operand that must be negated + before output. + If CODE is 'B' then output a bitwise inverted value of X (a const int). + If X is a REG and CODE is `M', output a ldm/stm style multi-reg. */ + +void +arm_print_operand (stream, x, code) + FILE *stream; + rtx x; + int code; +{ + switch (code) + { + case '@': + fputc (ARM_COMMENT_CHAR, stream); + return; + + case '|': + fputs (ARM_REG_PREFIX, stream); + return; + + case '?': + if (arm_ccfsm_state == 3 || arm_ccfsm_state == 4) + fputs (arm_condition_codes[arm_current_cc], stream); + return; + + case 'N': + { + REAL_VALUE_TYPE r; + REAL_VALUE_FROM_CONST_DOUBLE (r, x); + r = REAL_VALUE_NEGATE (r); + fprintf (stream, "%s", fp_const_from_val (&r)); + } + return; + + case 'B': + if (GET_CODE (x) == CONST_INT) + fprintf (stream, +#if HOST_BITS_PER_WIDE_INT == HOST_BITS_PER_INT + "%d", +#else + "%ld", +#endif + ARM_SIGN_EXTEND (~ INTVAL (x))); + else + { + putc ('~', stream); + output_addr_const (stream, x); + } + return; + + case 'i': + fprintf (stream, "%s", arithmetic_instr (x, 1)); + return; + + case 'I': + fprintf (stream, "%s", arithmetic_instr (x, 0)); + return; + + case 'S': + { + HOST_WIDE_INT val; + char *shift = shift_op (x, &val); + + if (shift) + { + fprintf (stream, ", %s ", shift_op (x, &val)); + if (val == -1) + arm_print_operand (stream, XEXP (x, 1), 0); + else + fprintf (stream, +#if HOST_BITS_PER_WIDE_INT == HOST_BITS_PER_INT + "#%d", +#else + "#%ld", +#endif + val); + } + } + return; + + case 'R': + if (REGNO (x) > 15) + abort (); + fputs (ARM_REG_PREFIX, stream); + fputs (reg_names[REGNO (x) + 1], stream); + return; + + case 'm': + fputs (ARM_REG_PREFIX, stream); + if (GET_CODE (XEXP (x, 0)) == REG) + fputs (reg_names[REGNO (XEXP (x, 0))], stream); + else + fputs (reg_names[REGNO (XEXP (XEXP (x, 0), 0))], stream); + return; + + case 'M': + fprintf (stream, "{%s%s-%s%s}", ARM_REG_PREFIX, reg_names[REGNO (x)], + ARM_REG_PREFIX, reg_names[REGNO (x) - 1 + + ((GET_MODE_SIZE (GET_MODE (x)) + + GET_MODE_SIZE (SImode) - 1) + / GET_MODE_SIZE (SImode))]); + return; + + case 'd': + if (x) + fputs (arm_condition_codes[get_arm_condition_code (x)], + stream); + return; + + case 'D': + if (x && (flag_fast_math + || GET_CODE (x) == EQ || GET_CODE (x) == NE + || (GET_MODE (XEXP (x, 0)) != CCFPEmode + && (GET_MODE_CLASS (GET_MODE (XEXP (x, 0))) + != MODE_FLOAT)))) + fputs (arm_condition_codes[ARM_INVERSE_CONDITION_CODE + (get_arm_condition_code (x))], + stream); + return; + + default: + if (x == 0) + abort (); + + if (GET_CODE (x) == REG) + { + fputs (ARM_REG_PREFIX, stream); + fputs (reg_names[REGNO (x)], stream); + } + else if (GET_CODE (x) == MEM) + { + output_memory_reference_mode = GET_MODE (x); + output_address (XEXP (x, 0)); + } + else if (GET_CODE (x) == CONST_DOUBLE) + fprintf (stream, "#%s", fp_immediate_constant (x)); + else if (GET_CODE (x) == NEG) + abort (); /* This should never happen now. */ + else + { + fputc ('#', stream); + output_addr_const (stream, x); + } + } +} + /* Increase the `arm_text_location' by AMOUNT if we're in the text segment. */ @@ -963,26 +3206,7 @@ arm_increase_location (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 @@ -1018,74 +3242,90 @@ arm_asm_output_label (stream, name) } for (s = real_name; *s; s++) hash += *s; + hash = hash % LABEL_HASH_SIZE; cur = (struct label_offset *) 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. */ +/* Load a symbol that is known to be in the text segment into a register. + This should never be called when not optimizing. */ char * -arm_output_llc (operands) +output_load_symbol (insn, operands) + rtx insn; rtx *operands; { - char *s, *name = XSTR (XEXP (operands[1], 0), 0); + char *s; + char *name = XSTR (operands[1], 0); struct label_offset *he; - int hash = 0, conditional = (arm_ccfsm_state == 3 || arm_ccfsm_state == 4); + int hash = 0; + int offset; + unsigned int mask, never_mask = 0xffffffff; + int shift, inst; + char buffer[100]; - if (*name != '*') + if (optimize == 0 || *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 (); + + offset = (arm_text_location + insn_addresses[INSN_UID (insn)] + + get_prologue_size () + 8 - he->offset); + if (offset < 0) + abort (); + + /* When generating the instructions, we never mask out the bits that we + think will be always zero, then if a mistake has occured somewhere, the + assembler will spot it and generate an error. */ - if (arm_text_location + 8 - he->offset < 4095) + /* If the symbol is word aligned then we might be able to reduce the + number of loads. */ + shift = ((offset & 3) == 0) ? 2 : 0; + + /* Clear the bits from NEVER_MASK that will be orred in with the individual + instructions. */ + for (; shift < 32; shift += 8) { - 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 (""); + mask = 0xff << shift; + if ((offset & mask) || ((unsigned) offset) > mask) + never_mask &= ~mask; } - 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. */ + inst = 8; + mask = 0xff << (shift - 32); - if (offset > 1000000) - abort (); + while (mask && (never_mask & mask) == 0) + { + if (inst == 8) + { + strcpy (buffer, "sub%?\t%0, %|pc, #(8 + . -%a1)"); + if ((never_mask | mask) != 0xffffffff) + sprintf (buffer + strlen (buffer), " & 0x%x", mask | never_mask); + } + else + sprintf (buffer, "sub%%?\t%%0, %%0, #(%d + . -%%a1) & 0x%x", + inst, mask | never_mask); - 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); + output_asm_insn (buffer, operands); + mask <<= 8; + inst -= 4; } - return (""); -} /* arm_output_llc */ + return ""; +} /* Output code resembling an .lcomm directive. /bin/as doesn't have this directive hence this hack, which works by reserving some `.space' in the @@ -1100,14 +3340,14 @@ output_lcomm_directive (stream, name, si char *name; int size, rounded; { - fputs ("\n\t.bss\t@ .lcomm\n", stream); + fprintf (stream, "\n\t.bss\t%c .lcomm\n", ARM_COMMENT_CHAR); 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 */ +} /* A finite state machine takes care of noticing whether or not instructions can be conditionally executed, and thus decrease execution time and code @@ -1131,20 +3371,15 @@ output_lcomm_directive (stream, name, si 4 -> 0 final_prescan_insn if the `target' unconditional branch is reached (the target insn is arm_target_insn). + If the jump clobbers the conditions then we use states 2 and 4. + + A similar thing can be done with conditional return insns. + 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 (...) (...))'. */ @@ -1169,7 +3404,7 @@ get_arm_condition_code (comparison) } /*NOTREACHED*/ return (42); -} /* get_arm_condition_code */ +} void @@ -1185,6 +3420,15 @@ final_prescan_insn (insn, opvec, noperan reversed if it appears to fail. */ int reverse = 0; + /* JUMP_CLOBBERS will be one implies that the conditions if a branch is + taken are clobbered, even if the rtl suggests otherwise. It also + means that we have to grub around within the jump expression to find + out what the conditions are when the jump isn't taken. */ + int jump_clobbers = 0; + + /* If we start with a return insn, we only succeed if we find another one. */ + int seeking_return = 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; @@ -1194,7 +3438,10 @@ final_prescan_insn (insn, opvec, noperan if (arm_ccfsm_state == 4) { if (insn == arm_target_insn) + { + arm_target_insn = NULL; arm_ccfsm_state = 0; + } return; } @@ -1219,6 +3466,21 @@ final_prescan_insn (insn, opvec, noperan else return; } + else if (GET_CODE (body) == RETURN) + { + start_insn = next_nonnote_insn (start_insn); + if (GET_CODE (start_insn) == 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; + seeking_return = 1; + } + else + return; + } else return; } @@ -1228,6 +3490,20 @@ final_prescan_insn (insn, opvec, noperan if (GET_CODE (insn) != JUMP_INSN) return; + /* This jump might be paralled with a clobber of the condition codes + the jump should always come first */ + if (GET_CODE (body) == PARALLEL && XVECLEN (body, 0) > 0) + body = XVECEXP (body, 0, 0); + +#if 0 + /* If this is a conditional return then we don't want to know */ + if (GET_CODE (body) == SET && GET_CODE (SET_DEST (body)) == PC + && GET_CODE (SET_SRC (body)) == IF_THEN_ELSE + && (GET_CODE (XEXP (SET_SRC (body), 1)) == RETURN + || GET_CODE (XEXP (SET_SRC (body), 2)) == RETURN)) + return; +#endif + if (reverse || (GET_CODE (body) == SET && GET_CODE (SET_DEST (body)) == PC && GET_CODE (SET_SRC (body)) == IF_THEN_ELSE)) @@ -1235,11 +3511,23 @@ final_prescan_insn (insn, opvec, noperan 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; + rtx this_insn = start_insn, label = 0; + if (get_attr_conds (insn) == CONDS_JUMP_CLOB) + { + /* The code below is wrong for these, and I haven't time to + fix it now. So we just do the safe thing and return. This + whole function needs re-writing anyway. */ + jump_clobbers = 1; + return; + } + /* Register the insn jumped to. */ if (reverse) - label = XEXP (SET_SRC (body), 0); + { + if (!seeking_return) + 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) @@ -1247,6 +3535,13 @@ final_prescan_insn (insn, opvec, noperan label = XEXP (XEXP (SET_SRC (body), 2), 0); then_not_else = FALSE; } + else if (GET_CODE (XEXP (SET_SRC (body), 1)) == RETURN) + seeking_return = 1; + else if (GET_CODE (XEXP (SET_SRC (body), 2)) == RETURN) + { + seeking_return = 1; + then_not_else = FALSE; + } else abort (); @@ -1272,33 +3567,54 @@ final_prescan_insn (insn, opvec, noperan control falls in from somewhere else. */ if (this_insn == label) { - arm_ccfsm_state = 1; + if (jump_clobbers) + { + arm_ccfsm_state = 2; + this_insn = next_nonnote_insn (this_insn); + } + else + arm_ccfsm_state = 1; succeed = TRUE; } else fail = TRUE; break; - case BARRIER: /* XXX Is this case necessary? */ + case BARRIER: /* Succeed if the following insn is the target label. - Otherwise fail. */ + Otherwise fail. + If return insns are used then the last insn in a function + will be a barrier. */ this_insn = next_nonnote_insn (this_insn); - if (this_insn == label) + if (this_insn && this_insn == label) { - arm_ccfsm_state = 1; + if (jump_clobbers) + { + arm_ccfsm_state = 2; + this_insn = next_nonnote_insn (this_insn); + } + else + arm_ccfsm_state = 1; succeed = TRUE; } else fail = TRUE; break; + case CALL_INSN: + /* The arm 6xx uses full 32 bit addresses so the cc is not + preserved over calls */ + if (TARGET_6) + 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 (scanbody) == SET + && GET_CODE (SET_DEST (scanbody)) == PC) { if (GET_CODE (SET_SRC (scanbody)) == LABEL_REF && XEXP (SET_SRC (scanbody), 0) == label && !reverse) @@ -1309,11 +3625,31 @@ final_prescan_insn (insn, opvec, noperan else if (GET_CODE (SET_SRC (scanbody)) == IF_THEN_ELSE) fail = TRUE; } + else if (GET_CODE (scanbody) == RETURN + && seeking_return) + { + arm_ccfsm_state = 2; + succeed = TRUE; + } + else if (GET_CODE (scanbody) == PARALLEL) + { + switch (get_attr_conds (this_insn)) + { + case CONDS_NOCOND: + break; + default: + fail = TRUE; + break; + } + } break; case INSN: - /* Instructions affecting the condition codes make it fail. */ - if (sets_cc0_p (scanbody)) + /* Instructions using or affecting the condition codes make it + fail. */ + if ((GET_CODE (scanbody) == SET + || GET_CODE (scanbody) == PARALLEL) + && get_attr_conds (this_insn) != CONDS_NOCOND) fail = TRUE; break; @@ -1323,21 +3659,59 @@ final_prescan_insn (insn, opvec, noperan } if (succeed) { - if (arm_ccfsm_state == 1 || reverse) + if ((!seeking_return) && (arm_ccfsm_state == 1 || reverse)) arm_target_label = CODE_LABEL_NUMBER (label); - else if (arm_ccfsm_state == 2) - arm_target_insn = this_insn; + else if (seeking_return || arm_ccfsm_state == 2) + { + while (this_insn && GET_CODE (PATTERN (this_insn)) == USE) + { + this_insn = next_nonnote_insn (this_insn); + if (this_insn && (GET_CODE (this_insn) == BARRIER + || GET_CODE (this_insn) == CODE_LABEL)) + abort (); + } + if (!this_insn) + { + /* Oh, dear! we ran off the end.. give up */ + recog (PATTERN (insn), insn, NULL_PTR); + arm_ccfsm_state = 0; + arm_target_insn = NULL; + return; + } + arm_target_insn = this_insn; + } else abort (); + if (jump_clobbers) + { + if (reverse) + abort (); + arm_current_cc = + get_arm_condition_code (XEXP (XEXP (XEXP (SET_SRC (body), + 0), 0), 1)); + if (GET_CODE (XEXP (XEXP (SET_SRC (body), 0), 0)) == AND) + arm_current_cc = ARM_INVERSE_CONDITION_CODE (arm_current_cc); + if (GET_CODE (XEXP (SET_SRC (body), 0)) == NE) + arm_current_cc = ARM_INVERSE_CONDITION_CODE (arm_current_cc); + } + else + { + /* 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 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); } + /* restore recog_operand (getting the attributes of other insns can + destroy this array, but final.c assumes that it remains intact + accross this call; since the insn has been recognized already we + call recog direct). */ + recog (PATTERN (insn), insn, NULL_PTR); } -} /* final_prescan_insn */ +} /* EOF */