diff options
author | law <law@138bc75d-0d04-0410-961f-82ee72b054a4> | 1997-06-23 17:29:13 +0000 |
---|---|---|
committer | law <law@138bc75d-0d04-0410-961f-82ee72b054a4> | 1997-06-23 17:29:13 +0000 |
commit | c418d182f7127f60d5d6c1e34fb70003166f05a5 (patch) | |
tree | 261e2aa99386dba2f43afbf53c900aea44a53247 /gcc/config/mn10200/mn10200.c | |
parent | eb56b12bde38d4d976fa16f95c4df9081eaa53ab (diff) | |
download | gcc-c418d182f7127f60d5d6c1e34fb70003166f05a5.tar.gz |
Initial revision
git-svn-id: svn+ssh://gcc.gnu.org/svn/gcc/trunk@14289 138bc75d-0d04-0410-961f-82ee72b054a4
Diffstat (limited to 'gcc/config/mn10200/mn10200.c')
-rw-r--r-- | gcc/config/mn10200/mn10200.c | 1542 |
1 files changed, 1542 insertions, 0 deletions
diff --git a/gcc/config/mn10200/mn10200.c b/gcc/config/mn10200/mn10200.c new file mode 100644 index 00000000000..6c929c81136 --- /dev/null +++ b/gcc/config/mn10200/mn10200.c @@ -0,0 +1,1542 @@ +/* Subroutines for insn-output.c for Matsushita MN10200 series + Copyright (C) 1997 Free Software Foundation, Inc. + Contributed by Jeff Law (law@cygnus.com). + +This file is part of GNU CC. + +GNU CC is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU CC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU CC; see the file COPYING. If not, write to +the Free Software Foundation, 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. */ + +#include <stdio.h> +#include "config.h" +#include "rtl.h" +#include "regs.h" +#include "hard-reg-set.h" +#include "real.h" +#include "insn-config.h" +#include "conditions.h" +#include "insn-flags.h" +#include "output.h" +#include "insn-attr.h" +#include "flags.h" +#include "recog.h" +#include "expr.h" +#include "tree.h" +#include "obstack.h" + +/* Global registers known to hold the value zero. + + Normally we'd depend on CSE and combine to put zero into a + register and re-use it. + + However, on the mn10x00 processors we implicitly use the constant + zero in tst instructions, so we might be able to do better by + loading the value into a register in the prologue, then re-useing + that register throughout the function. + + We could perform similar optimizations for other constants, but with + gcse due soon, it doesn't seem worth the effort. + + These variables hold a rtx for a register known to hold the value + zero throughout the entire function, or NULL if no register of + the appropriate class has such a value throughout the life of the + function. */ +rtx zero_dreg; +rtx zero_areg; + +/* Note whether or not we need an out of line epilogue. */ +static int out_of_line_epilogue; + +/* Indicate this file was compiled by gcc and what optimization + level was used. */ +void +asm_file_start (file) + FILE *file; +{ + fprintf (file, "#\tGCC For the Matsushita MN10200\n"); + if (optimize) + fprintf (file, "# -O%d\n", optimize); + else + fprintf (file, "\n\n"); + output_file_directive (file, main_input_filename); +} + +/* Print operand X using operand code CODE to assembly language output file + FILE. */ + +void +print_operand (file, x, code) + FILE *file; + rtx x; + int code; +{ + switch (code) + { + case 'b': + case 'B': + /* These are normal and reversed branches. */ + switch (code == 'b' ? GET_CODE (x) : reverse_condition (GET_CODE (x))) + { + case NE: + fprintf (file, "ne"); + break; + case EQ: + fprintf (file, "eq"); + break; + case GE: + fprintf (file, "ge"); + break; + case GT: + fprintf (file, "gt"); + break; + case LE: + fprintf (file, "le"); + break; + case LT: + fprintf (file, "lt"); + break; + case GEU: + fprintf (file, "cc"); + break; + case GTU: + fprintf (file, "hi"); + break; + case LEU: + fprintf (file, "ls"); + break; + case LTU: + fprintf (file, "cs"); + break; + default: + abort (); + } + break; + case 'C': + /* This is used for the operand to a call instruction; + if it's a REG, enclose it in parens, else output + the operand normally. */ + if (GET_CODE (x) == REG) + { + fputc ('(', file); + print_operand (file, x, 0); + fputc (')', file); + } + else + print_operand (file, x, 0); + break; + + /* These are the least significant word in a 32bit value. + 'o' allows us to sign extend a constant if doing so + makes for more compact code. */ + case 'L': + case 'o': + switch (GET_CODE (x)) + { + case MEM: + fputc ('(', file); + output_address (XEXP (x, 0)); + fputc (')', file); + break; + + case REG: + fprintf (file, "%s", reg_names[REGNO (x)]); + break; + + case SUBREG: + fprintf (file, "%s", + reg_names[REGNO (SUBREG_REG (x)) + SUBREG_WORD (x)]); + break; + + case CONST_DOUBLE: + if (code == 'L') + { + long val; + REAL_VALUE_TYPE rv; + + REAL_VALUE_FROM_CONST_DOUBLE (rv, x); + REAL_VALUE_TO_TARGET_SINGLE (rv, val); + print_operand_address (file, GEN_INT (val & 0xffff)); + } + else + { + long val; + REAL_VALUE_TYPE rv; + + REAL_VALUE_FROM_CONST_DOUBLE (rv, x); + REAL_VALUE_TO_TARGET_SINGLE (rv, val); + + val &= 0xffff; + val = (((val) & 0xffff) ^ (~0x7fff)) + 0x8000; + print_operand_address (file, GEN_INT (val)); + } + break; + + case CONST_INT: + if (code == 'L') + print_operand_address (file, GEN_INT ((INTVAL (x) & 0xffff))); + else + { + unsigned int val = INTVAL (x) & 0xffff; + val = (((val) & 0xffff) ^ (~0x7fff)) + 0x8000; + print_operand_address (file, GEN_INT (val)); + } + break; + default: + abort (); + } + break; + + /* Similarly, but for the most significant word. */ + case 'H': + case 'h': + switch (GET_CODE (x)) + { + case MEM: + fputc ('(', file); + x = adj_offsettable_operand (x, 2); + output_address (XEXP (x, 0)); + fputc (')', file); + break; + + case REG: + fprintf (file, "%s", reg_names[REGNO (x) + 1]); + break; + + case SUBREG: + fprintf (file, "%s", + reg_names[REGNO (SUBREG_REG (x)) + SUBREG_WORD (x)] + 1); + break; + + case CONST_DOUBLE: + if (code == 'H') + { + long val; + REAL_VALUE_TYPE rv; + + REAL_VALUE_FROM_CONST_DOUBLE (rv, x); + REAL_VALUE_TO_TARGET_SINGLE (rv, val); + + print_operand_address (file, GEN_INT ((val >> 16) & 0xffff)); + } + else + { + long val; + REAL_VALUE_TYPE rv; + + REAL_VALUE_FROM_CONST_DOUBLE (rv, x); + REAL_VALUE_TO_TARGET_SINGLE (rv, val); + + val = (val >> 16) & 0xffff; + val = (((val) & 0xffff) ^ (~0x7fff)) + 0x8000; + + print_operand_address (file, GEN_INT (val)); + } + break; + + case CONST_INT: + if (code == 'H') + print_operand_address (file, + GEN_INT ((INTVAL (x) >> 16) & 0xffff)); + else + { + unsigned int val = (INTVAL (x) >> 16) & 0xffff; + val = (((val) & 0xffff) ^ (~0x7fff)) + 0x8000; + + print_operand_address (file, GEN_INT (val)); + } + break; + default: + abort (); + } + break; + + /* Output ~CONST_INT. */ + case 'N': + if (GET_CODE (x) != CONST_INT) + abort (); + fprintf (file, "%d", ~INTVAL (x)); + break; + + /* An address which can not be register indirect, if it is + register indirect, then turn it into reg + disp. */ + case 'A': + if (GET_CODE (x) != MEM) + abort (); + if (GET_CODE (XEXP (x, 0)) == REG) + x = gen_rtx (PLUS, PSImode, XEXP (x, 0), GEN_INT (0)); + else + x = XEXP (x, 0); + fputc ('(', file); + output_address (x); + fputc (')', file); + break; + + case 'Z': + print_operand (file, XEXP (x, 1), 0); + break; + + /* More cases where we can sign-extend a CONST_INT if it + results in more compact code. */ + case 's': + case 'S': + if (GET_CODE (x) == CONST_INT) + { + int val = INTVAL (x); + + if (code == 's') + x = GEN_INT (((val & 0xffff) ^ (~0x7fff)) + 0x8000); + else + x = GEN_INT (((val & 0xff) ^ (~0x7f)) + 0x80); + } + /* FALL THROUGH */ + default: + switch (GET_CODE (x)) + { + case MEM: + fputc ('(', file); + output_address (XEXP (x, 0)); + fputc (')', file); + break; + + case REG: + fprintf (file, "%s", reg_names[REGNO (x)]); + break; + + case SUBREG: + fprintf (file, "%s", + reg_names[REGNO (SUBREG_REG (x)) + SUBREG_WORD (x)]); + break; + + case CONST_INT: + case CONST_DOUBLE: + case SYMBOL_REF: + case CONST: + case LABEL_REF: + case CODE_LABEL: + print_operand_address (file, x); + break; + default: + abort (); + } + break; + } +} + +/* Output assembly language output for the address ADDR to FILE. */ + +void +print_operand_address (file, addr) + FILE *file; + rtx addr; +{ + switch (GET_CODE (addr)) + { + case REG: + print_operand (file, addr, 0); + break; + case PLUS: + { + rtx base, index; + /* The base and index could be in any order, so we have + to figure out which is the base and which is the index. + Uses the same code as GO_IF_LEGITIMATE_ADDRESS. */ + if (REG_P (XEXP (addr, 0)) + && REG_OK_FOR_BASE_P (XEXP (addr, 0))) + base = XEXP (addr, 0), index = XEXP (addr, 1); + else if (REG_P (XEXP (addr, 1)) + && REG_OK_FOR_BASE_P (XEXP (addr, 1))) + base = XEXP (addr, 1), index = XEXP (addr, 0); + else + abort (); + print_operand (file, index, 0); + fputc (',', file); + print_operand (file, base, 0);; + break; + } + case SYMBOL_REF: + output_addr_const (file, addr); + break; + default: + output_addr_const (file, addr); + break; + } +} + +/* Count the number of tst insns which compare an address register + with zero. */ +static void +count_tst_insns (areg_countp) + int *areg_countp; +{ + rtx insn; + + /* Assume no tst insns exist. */ + *areg_countp = 0; + + /* If not optimizing, then quit now. */ + if (!optimize) + return; + + /* Walk through all the insns. */ + for (insn = get_insns (); insn; insn = NEXT_INSN (insn)) + { + rtx pat; + + /* Ignore anything that is not a normal INSN. */ + if (GET_CODE (insn) != INSN) + continue; + + /* Ignore anything that isn't a SET. */ + pat = PATTERN (insn); + if (GET_CODE (pat) != SET) + continue; + + /* Check for a tst insn. */ + if (SET_DEST (pat) == cc0_rtx + && GET_CODE (SET_SRC (pat)) == REG + && REGNO_REG_CLASS (REGNO (SET_SRC (pat))) == ADDRESS_REGS) + (*areg_countp)++; + } +} + +/* Return the total size (in bytes) of the current function's frame. + This is the size of the register save area + the size of locals, + spills, etc. */ +int +total_frame_size () +{ + unsigned int size = get_frame_size (); + unsigned int outgoing_args_size = current_function_outgoing_args_size; + int i; + + /* First figure out if we're going to use an out of line + prologue, if so we have to make space for all the + registers, even if we don't use them. */ + if (optimize && !current_function_needs_context && !frame_pointer_needed) + { + int inline_count, outline_count; + + /* Compute how many bytes an inline prologue would take. + + Each address register store takes two bytes, each data register + store takes three bytes. */ + inline_count = 0; + if (regs_ever_live[5]) + inline_count += 2; + if (regs_ever_live[6]) + inline_count += 2; + if (regs_ever_live[2]) + inline_count += 3; + if (regs_ever_live[3]) + inline_count += 3; + + /* If this function has any stack, then the stack adjustment + will take two (or more) bytes. */ + if (size || outgoing_args_size + || regs_ever_live[5] || regs_ever_live[6] + || regs_ever_live[2] || regs_ever_live[3]) + inline_count += 2; + + /* Multiply the current count by two and add one to account for the + epilogue insns. */ + inline_count = inline_count * 2 + 1; + + /* Now compute how many bytes an out of line sequence would take. */ + /* A relaxed jsr will be three bytes. */ + outline_count = 3; + + /* If there are outgoing arguments, then we will need a stack + pointer adjustment after the call to the prologue, two + more bytes. */ + outline_count += (outgoing_args_size == 0 ? 0 : 2); + + /* If there is some local frame to allocate, it will need to be + done before the call to the prologue, two more bytes. */ + if (get_frame_size () != 0) + outline_count += 2; + + /* Now account for the epilogue, multiply the base count by two, + then deal with optimizing away the rts instruction. */ + outline_count = outline_count * 2 + 1; + + if (get_frame_size () == 0 && outgoing_args_size == 0) + outline_count -= 1; + + /* If an out of line prologue is smaller, use it. */ + if (inline_count > outline_count) + return size + outgoing_args_size + 16; + } + + + for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) + { + if (regs_ever_live[i] && !call_used_regs[i] && ! fixed_regs[i] + || (i == FRAME_POINTER_REGNUM && frame_pointer_needed)) + size += 4; + } + + return (size + outgoing_args_size); +} + +/* Expand the prologue into RTL. */ +void +expand_prologue () +{ + unsigned int size = total_frame_size (); + unsigned int outgoing_args_size = current_function_outgoing_args_size; + int offset, i; + + zero_areg = NULL_RTX; + zero_dreg = NULL_RTX; + + /* If optimizing, see if we should do an out of line prologue/epilogue + sequence. + + We don't support out of line prologues if the current function + needs a context or frame pointer. */ + if (optimize && !current_function_needs_context && !frame_pointer_needed) + { + int inline_count, outline_count, areg_count; + + /* We need to end the current sequence so that count_tst_insns can + look at all the insns in this function. Normally this would be + unsafe, but it's OK in the prologue/epilogue expanders. */ + end_sequence (); + + /* Get a count of the number of tst insns which use address + registers (it's not profitable to try and improve tst insns + which use data registers). */ + count_tst_insns (&areg_count); + + /* Now start a new sequence. */ + start_sequence (); + + /* Compute how many bytes an inline prologue would take. + + Each address register store takes two bytes, each data register + store takes three bytes. */ + inline_count = 0; + if (regs_ever_live[5]) + inline_count += 2; + if (regs_ever_live[6]) + inline_count += 2; + if (regs_ever_live[2]) + inline_count += 3; + if (regs_ever_live[3]) + inline_count += 3; + + /* If this function has any stack, then the stack adjustment + will take two (or more) bytes. */ + if (size || outgoing_args_size + || regs_ever_live[5] || regs_ever_live[6] + || regs_ever_live[2] || regs_ever_live[3]) + inline_count += 2; + + /* Multiply the current count by two and add one to account for the + epilogue insns. */ + inline_count = inline_count * 2 + 1; + + /* Now compute how many bytes an out of line sequence would take. */ + /* A relaxed jsr will be three bytes. */ + outline_count = 3; + + /* If there are outgoing arguments, then we will need a stack + pointer adjustment after the call to the prologue, two + more bytes. */ + outline_count += (outgoing_args_size == 0 ? 0 : 2); + + /* If there is some local frame to allocate, it will need to be + done before the call to the prologue, two more bytes. */ + if (get_frame_size () != 0) + outline_count += 2; + + /* Now account for the epilogue, multiply the base count by two, + then deal with optimizing away the rts instruction. */ + outline_count = outline_count * 2 + 1; + + if (get_frame_size () == 0 && outgoing_args_size == 0) + outline_count -= 1; + + /* If an out of line prologue is smaller, use it. */ + if (inline_count > outline_count) + { + if (get_frame_size () != 0) + emit_insn (gen_addpsi3 (stack_pointer_rtx, stack_pointer_rtx, + GEN_INT (-size + outgoing_args_size + 16))); + emit_insn (gen_outline_prologue_call ()); + + if (outgoing_args_size) + emit_insn (gen_addpsi3 (stack_pointer_rtx, stack_pointer_rtx, + GEN_INT (-outgoing_args_size))); + + out_of_line_epilogue = 1; + + /* Determine if it is profitable to put the value zero into a register + for the entire function. If so, set ZERO_DREG and ZERO_AREG. */ + + /* First see if we could load the value into a data register + since that's the most efficient way. */ + if (areg_count > 1 + && (!regs_ever_live[2] || !regs_ever_live[3])) + { + if (!regs_ever_live[2]) + { + regs_ever_live[2] = 1; + zero_dreg = gen_rtx (REG, HImode, 2); + } + if (!regs_ever_live[3]) + { + regs_ever_live[3] = 1; + zero_dreg = gen_rtx (REG, HImode, 3); + } + } + + /* Now see if we could load the value into a address register. */ + if (zero_dreg == NULL_RTX + && areg_count > 2 + && (!regs_ever_live[5] || !regs_ever_live[6])) + { + if (!regs_ever_live[5]) + { + regs_ever_live[5] = 1; + zero_dreg = gen_rtx (REG, HImode, 5); + } + if (!regs_ever_live[6]) + { + regs_ever_live[6] = 1; + zero_dreg = gen_rtx (REG, HImode, 6); + } + } + + if (zero_dreg) + emit_move_insn (zero_dreg, const0_rtx); + + if (zero_areg) + emit_move_insn (zero_areg, const0_rtx); + + return; + } + } + + out_of_line_epilogue = 0; + + /* Temporarily stuff the static chain onto the stack so we can + use a0 as a scratch register during the prologue. */ + if (current_function_needs_context) + { + emit_insn (gen_addpsi3 (stack_pointer_rtx, stack_pointer_rtx, + GEN_INT (-4))); + emit_move_insn (gen_rtx (MEM, PSImode, stack_pointer_rtx), + gen_rtx (REG, PSImode, STATIC_CHAIN_REGNUM)); + } + + if (frame_pointer_needed) + { + /* Store a2 into a0 temporarily. */ + emit_move_insn (gen_rtx (REG, PSImode, 4), frame_pointer_rtx); + + /* Set up the frame pointer. */ + emit_move_insn (frame_pointer_rtx, stack_pointer_rtx); + } + + /* Make any necessary space for the saved registers and local frame. */ + if (size) + emit_insn (gen_addpsi3 (stack_pointer_rtx, stack_pointer_rtx, + GEN_INT (-size))); + + /* Save the callee saved registers. They're saved into the top + of the frame, using the stack pointer. */ + for (i = 0, offset = outgoing_args_size; + i < FIRST_PSEUDO_REGISTER; i++) + { + if (regs_ever_live[i] && !call_used_regs[i] && ! fixed_regs[i] + || (i == FRAME_POINTER_REGNUM && frame_pointer_needed)) + { + int regno; + + /* If we're saving the frame pointer, then it will be found in + register 4 (a0). */ + regno = (i == FRAME_POINTER_REGNUM && frame_pointer_needed) ? 4 : i; + + emit_move_insn (gen_rtx (MEM, PSImode, + gen_rtx (PLUS, Pmode, + stack_pointer_rtx, + GEN_INT (offset))), + gen_rtx (REG, PSImode, regno)); + offset += 4; + } + } + + /* Now put the static chain back where the rest of the function + expects to find it. */ + if (current_function_needs_context) + { + emit_move_insn (gen_rtx (REG, PSImode, STATIC_CHAIN_REGNUM), + gen_rtx (MEM, PSImode, + gen_rtx (PLUS, PSImode, stack_pointer_rtx, + GEN_INT (size)))); + } +} + +/* Expand the epilogue into RTL. */ +void +expand_epilogue () +{ + unsigned int size; + unsigned int outgoing_args_size = current_function_outgoing_args_size; + int offset, i, temp_regno; + rtx basereg; + + size = total_frame_size (); + + if (DECL_RESULT (current_function_decl) + && DECL_RTL (DECL_RESULT (current_function_decl)) + && REG_P (DECL_RTL (DECL_RESULT (current_function_decl)))) + temp_regno = (REGNO (DECL_RTL (DECL_RESULT (current_function_decl))) == 4 + ? 0 : 4); + else + temp_regno = 4; + + /* Emit an out of line epilogue sequence if it's profitable to do so. */ + if (out_of_line_epilogue) + { + /* If there were no outgoing arguments and no local frame, then + we will be able to omit the rts at the end of this function, + so just jump to the epilogue_noreturn routine. */ + if (get_frame_size () == 0 && outgoing_args_size == 0) + { + emit_jump_insn (gen_outline_epilogue_jump ()); + return; + } + + if (outgoing_args_size) + emit_insn (gen_addpsi3 (stack_pointer_rtx, stack_pointer_rtx, + GEN_INT (outgoing_args_size))); + + if (temp_regno == 0) + emit_insn (gen_outline_epilogue_call_d0 ()); + else if (temp_regno == 4) + emit_insn (gen_outline_epilogue_call_a0 ()); + + if (get_frame_size () != 0) + emit_insn (gen_addpsi3 (stack_pointer_rtx, stack_pointer_rtx, + GEN_INT (size - outgoing_args_size - 16))); + emit_jump_insn (gen_return_internal ()); + return; + } + + /* Registers are restored from the frame pointer if we have one, + else they're restored from the stack pointer. Figure out + the appropriate offset to the register save area for both cases. */ + if (frame_pointer_needed) + { + basereg = frame_pointer_rtx; + offset = -(size - outgoing_args_size); + } + else + { + basereg = stack_pointer_rtx; + offset = outgoing_args_size; + } + + /* Restore each register. */ + for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) + { + if (regs_ever_live[i] && !call_used_regs[i] && ! fixed_regs[i] + || (i == FRAME_POINTER_REGNUM && frame_pointer_needed)) + { + int regno; + + /* Restore the frame pointer (if it exists) into a temporary + register. */ + regno = ((i == FRAME_POINTER_REGNUM && frame_pointer_needed) + ? temp_regno : i); + + emit_move_insn (gen_rtx (REG, PSImode, regno), + gen_rtx (MEM, PSImode, + gen_rtx (PLUS, Pmode, + basereg, + GEN_INT (offset)))); + offset += 4; + } + } + + if (frame_pointer_needed) + { + /* Deallocate this frame's stack. */ + emit_move_insn (stack_pointer_rtx, frame_pointer_rtx); + /* Restore the old frame pointer. */ + emit_move_insn (frame_pointer_rtx, gen_rtx (REG, PSImode, temp_regno)); + } + else if (size) + { + /* Deallocate this function's stack. */ + emit_insn (gen_addpsi3 (stack_pointer_rtx, stack_pointer_rtx, + GEN_INT (size))); + } + + /* If we had to allocate a slot to save the context pointer, + then it must be deallocated here. */ + if (current_function_needs_context) + emit_insn (gen_addpsi3 (stack_pointer_rtx, stack_pointer_rtx, GEN_INT (4))); + + /* Emit the return insn, if this function had no stack, then we + can use the standard return (which allows more optimizations), + else we have to use the special one which inhibits optimizations. */ + if (size == 0 && !current_function_needs_context) + emit_jump_insn (gen_return ()); + else + emit_jump_insn (gen_return_internal ()); +} + +/* Update the condition code from the insn. */ + +void +notice_update_cc (body, insn) + rtx body; + rtx insn; +{ + switch (get_attr_cc (insn)) + { + case CC_NONE: + /* Insn does not affect CC at all. */ + break; + + case CC_NONE_0HIT: + /* Insn does not change CC, but the 0'th operand has been changed. */ + if (cc_status.value1 != 0 + && reg_overlap_mentioned_p (recog_operand[0], cc_status.value1)) + cc_status.value1 = 0; + break; + + case CC_SET_ZN: + /* Insn sets the Z,N flags of CC to recog_operand[0]. + V,C is in an unusable state. */ + CC_STATUS_INIT; + cc_status.flags |= CC_OVERFLOW_UNUSABLE | CC_NO_CARRY; + cc_status.value1 = recog_operand[0]; + break; + + case CC_SET_ZNV: + /* Insn sets the Z,N,V flags of CC to recog_operand[0]. + C is in an unusable state. */ + CC_STATUS_INIT; + cc_status.flags |= CC_NO_CARRY; + cc_status.value1 = recog_operand[0]; + break; + + case CC_COMPARE: + /* The insn is a compare instruction. */ + CC_STATUS_INIT; + cc_status.value1 = SET_SRC (body); + break; + + case CC_CLOBBER: + /* Insn doesn't leave CC in a usable state. */ + CC_STATUS_INIT; + break; + + default: + CC_STATUS_INIT; + break; + } +} + +/* Return true if OP is a valid call operand. Valid call operands + are SYMBOL_REFs and REGs. */ +int +call_address_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return (GET_CODE (op) == SYMBOL_REF || GET_CODE (op) == REG); +} + +/* Return true if OP is an indirect memory operand, the "bset" and "bclr" + insns use this predicate. */ +int +indirect_memory_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return (GET_CODE (op) == MEM && GET_CODE (XEXP (op, 0)) == REG); +} + +/* Return true if OP is a memory operand with a constant address. + A special PSImode move pattern uses this predicate. */ +int +constant_memory_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return GET_CODE (op) == MEM && CONSTANT_ADDRESS_P (XEXP (op, 0)); +} + +/* What (if any) secondary registers are needed to move IN with mode + MODE into a register from in register class CLASS. + + We might be able to simplify this. */ +enum reg_class +secondary_reload_class (class, mode, in, input) + enum reg_class class; + enum machine_mode mode; + rtx in; + int input; +{ + int regno; + + /* Memory loads less than a full word wide can't have an + address or stack pointer destination. They must use + a data register as an intermediate register. */ + if (input + && GET_CODE (in) == MEM + && (mode == QImode) + && class == ADDRESS_REGS) + return DATA_REGS; + + /* Address register stores which are not PSImode need a scrach register. */ + if (! input + && GET_CODE (in) == MEM + && (mode != PSImode) + && class == ADDRESS_REGS) + return DATA_REGS; + + /* Otherwise assume no secondary reloads are needed. */ + return NO_REGS; +} + + +/* Shifts. + + We devote a fair bit of code to getting efficient shifts since we can only + shift one bit at a time, and each single bit shift may take multiple + instructions. + + The basic shift methods: + + * loop shifts -- emit a loop using one (or two on H8/S) bit shifts; + this is the default. SHIFT_LOOP + + * inlined shifts -- emit straight line code for the shift; this is + used when a straight line shift is about the same size or smaller + than a loop. We allow the inline version to be slightly longer in + some cases as it saves a register. SHIFT_INLINE + + * There other oddballs. Not worth explaining. SHIFT_SPECIAL + + + HImode shifts: + + 1-4 do them inline + + 5-7 If ashift, then multiply, else loop. + + 8-14 - If ashift, then multiply, if lshiftrt, then divide, else loop. + 15 - rotate the bit we want into the carry, clear the destination, + (use mov 0,dst, not sub as sub will clobber the carry), then + move bit into place. + + Don't Panic, it's not nearly as bad as the H8 shifting code!!! */ + +int +nshift_operator (x, mode) + rtx x; + enum machine_mode mode; +{ + switch (GET_CODE (x)) + { + case ASHIFTRT: + case LSHIFTRT: + case ASHIFT: + return 1; + + default: + return 0; + } +} + +/* Called from the .md file to emit code to do shifts. + Returns a boolean indicating success + (currently this is always TRUE). */ + +int +expand_a_shift (mode, code, operands) + enum machine_mode mode; + int code; + rtx operands[]; +{ + emit_move_insn (operands[0], operands[1]); + + /* need a loop to get all the bits we want - we generate the + code at emit time, but need to allocate a scratch reg now */ + + emit_insn (gen_rtx + (PARALLEL, VOIDmode, + gen_rtvec (2, + gen_rtx (SET, VOIDmode, operands[0], + gen_rtx (code, mode, + operands[0], operands[2])), + gen_rtx (CLOBBER, VOIDmode, + gen_rtx (SCRATCH, HImode, 0))))); + + return 1; +} + +/* Shift algorithm determination. + + There are various ways of doing a shift: + SHIFT_INLINE: If the amount is small enough, just generate as many one-bit + shifts as we need. + SHIFT_SPECIAL: Hand crafted assembler. + SHIFT_LOOP: If the above methods fail, just loop. */ + +enum shift_alg +{ + SHIFT_INLINE, + SHIFT_SPECIAL, + SHIFT_LOOP, + SHIFT_MAX +}; + +/* Symbols of the various shifts which can be used as indices. */ + +enum shift_type + { + SHIFT_ASHIFT, SHIFT_LSHIFTRT, SHIFT_ASHIFTRT + }; + +/* Symbols of the various modes which can be used as indices. */ + +enum shift_mode + { + HIshift, + }; + +/* For single bit shift insns, record assembler and what bits of the + condition code are valid afterwards (represented as various CC_FOO + bits, 0 means CC isn't left in a usable state). */ + +struct shift_insn +{ + char *assembler; + int cc_valid; +}; + +/* Assembler instruction shift table. + + These tables are used to look up the basic shifts. + They are indexed by cpu, shift_type, and mode. +*/ + +static const struct shift_insn shift_one[3][3] = +{ + { +/* SHIFT_ASHIFT */ + { "add\t%0,%0", CC_OVERFLOW_UNUSABLE | CC_NO_CARRY }, + }, +/* SHIFT_LSHIFTRT */ + { + { "lsr\t%0", CC_NO_CARRY }, + }, +/* SHIFT_ASHIFTRT */ + { + { "asr\t%0", CC_NO_CARRY }, + }, +}; + +/* Given CPU, MODE, SHIFT_TYPE, and shift count COUNT, determine the best + algorithm for doing the shift. The assembler code is stored in ASSEMBLER. + We don't achieve maximum efficiency in all cases, but the hooks are here + to do so. + + For now we just use lots of switch statements. Since we don't even come + close to supporting all the cases, this is simplest. If this function ever + gets too big, perhaps resort to a more table based lookup. Of course, + at this point you may just wish to do it all in rtl. */ + +static enum shift_alg +get_shift_alg (shift_type, mode, count, assembler_p, cc_valid_p) + enum shift_type shift_type; + enum machine_mode mode; + int count; + const char **assembler_p; + int *cc_valid_p; +{ + /* The default is to loop. */ + enum shift_alg alg = SHIFT_LOOP; + enum shift_mode shift_mode; + + /* We don't handle negative shifts or shifts greater than the word size, + they should have been handled already. */ + + if (count < 0 || count > GET_MODE_BITSIZE (mode)) + abort (); + + switch (mode) + { + case HImode: + shift_mode = HIshift; + break; + default: + abort (); + } + + /* Assume either SHIFT_LOOP or SHIFT_INLINE. + It is up to the caller to know that looping clobbers cc. */ + *assembler_p = shift_one[shift_type][shift_mode].assembler; + *cc_valid_p = shift_one[shift_type][shift_mode].cc_valid; + + /* Now look for cases we want to optimize. */ + + switch (shift_mode) + { + case HIshift: + if (count <= 4) + return SHIFT_INLINE; + else if (count < 15 && shift_type != SHIFT_ASHIFTRT) + { + switch (count) + { + case 5: + if (shift_type == SHIFT_ASHIFT) + *assembler_p = "mov 32,%4\n\tmul %4,%0"; + else if (shift_type == SHIFT_LSHIFTRT) + *assembler_p + = "sub %4,%4\n\tmov %4,mdr\n\tmov 32,%4\n\tdivu %4,%0"; + *cc_valid_p = CC_NO_CARRY; + return SHIFT_SPECIAL; + case 6: + if (shift_type == SHIFT_ASHIFT) + *assembler_p = "mov 64,%4\n\tmul %4,%0"; + else if (shift_type == SHIFT_LSHIFTRT) + *assembler_p + = "sub %4,%4\n\tmov %4,mdr\n\tmov 64,%4\n\tdivu %4,%0"; + *cc_valid_p = CC_NO_CARRY; + return SHIFT_SPECIAL; + case 7: + if (shift_type == SHIFT_ASHIFT) + *assembler_p = "mov 128,%4\n\tmul %4,%0"; + else if (shift_type == SHIFT_LSHIFTRT) + *assembler_p + = "sub %4,%4\n\tmov %4,mdr\n\tmov 128,%4\n\tdivu %4,%0"; + *cc_valid_p = CC_NO_CARRY; + return SHIFT_SPECIAL; + case 8: + if (shift_type == SHIFT_ASHIFT) + *assembler_p = "mov 256,%4\n\tmul %4,%0"; + else if (shift_type == SHIFT_LSHIFTRT) + *assembler_p + = "sub %4,%4\n\tmov %4,mdr\n\tmov 256,%4\n\tdivu %4,%0"; + *cc_valid_p = CC_NO_CARRY; + return SHIFT_SPECIAL; + case 9: + if (shift_type == SHIFT_ASHIFT) + *assembler_p = "mov 512,%4\n\tmul %4,%0"; + else if (shift_type == SHIFT_LSHIFTRT) + *assembler_p + = "sub %4,%4\n\tmov %4,mdr\n\tmov 512,%4\n\tdivu %4,%0"; + *cc_valid_p = CC_NO_CARRY; + return SHIFT_SPECIAL; + case 10: + if (shift_type == SHIFT_ASHIFT) + *assembler_p = "mov 1024,%4\n\tmul %4,%0"; + else if (shift_type == SHIFT_LSHIFTRT) + *assembler_p + = "sub %4,%4\n\tmov %4,mdr\n\tmov 1024,%4\n\tdivu %4,%0"; + *cc_valid_p = CC_NO_CARRY; + return SHIFT_SPECIAL; + case 11: + if (shift_type == SHIFT_ASHIFT) + *assembler_p = "mov 2048,%4\n\tmul %4,%0"; + else if (shift_type == SHIFT_LSHIFTRT) + *assembler_p + = "sub %4,%4\n\tmov %4,mdr\n\tmov 2048,%4\n\tdivu %4,%0"; + *cc_valid_p = CC_NO_CARRY; + return SHIFT_SPECIAL; + case 12: + if (shift_type == SHIFT_ASHIFT) + *assembler_p = "mov 4096,%4\n\tmul %4,%0"; + else if (shift_type == SHIFT_LSHIFTRT) + *assembler_p + = "sub %4,%4\n\tmov %4,mdr\n\tmov 4096,%4\n\tdivu %4,%0"; + *cc_valid_p = CC_NO_CARRY; + return SHIFT_SPECIAL; + case 13: + if (shift_type == SHIFT_ASHIFT) + *assembler_p = "mov 8192,%4\n\tmul %4,%0"; + else if (shift_type == SHIFT_LSHIFTRT) + *assembler_p + = "sub %4,%4\n\tmov %4,mdr\n\tmov 8192,%4\n\tdivu %4,%0"; + *cc_valid_p = CC_NO_CARRY; + return SHIFT_SPECIAL; + case 14: + if (shift_type == SHIFT_ASHIFT) + *assembler_p = "mov 16384,%4\n\tmul %4,%0"; + else if (shift_type == SHIFT_LSHIFTRT) + *assembler_p + = "sub %4,%4\n\tmov %4,mdr\n\tmov 16384,%4\n\tdivu %4,%0"; + *cc_valid_p = CC_NO_CARRY; + return SHIFT_SPECIAL; + } + } + else if (count == 15) + { + if (shift_type == SHIFT_ASHIFTRT) + { + *assembler_p = "add\t%0,%0\n\tsubc\t%0,%0\n"; + *cc_valid_p = CC_NO_CARRY; + return SHIFT_SPECIAL; + } + if (shift_type == SHIFT_LSHIFTRT) + { + *assembler_p = "add\t%0,%0\n\tmov 0,%0\n\trol %0\n"; + *cc_valid_p = CC_NO_CARRY; + return SHIFT_SPECIAL; + } + if (shift_type == SHIFT_ASHIFT) + { + *assembler_p = "ror\t%0\n\tmov 0,%0\n\tror %0\n"; + *cc_valid_p = CC_NO_CARRY; + return SHIFT_SPECIAL; + } + } + break; + + default: + abort (); + } + + return alg; +} + +/* Emit the assembler code for doing shifts. */ + +char * +emit_a_shift (insn, operands) + rtx insn; + rtx *operands; +{ + static int loopend_lab; + char *assembler; + int cc_valid; + rtx inside = PATTERN (insn); + rtx shift = operands[3]; + enum machine_mode mode = GET_MODE (shift); + enum rtx_code code = GET_CODE (shift); + enum shift_type shift_type; + enum shift_mode shift_mode; + + loopend_lab++; + + switch (mode) + { + case HImode: + shift_mode = HIshift; + break; + default: + abort (); + } + + switch (code) + { + case ASHIFTRT: + shift_type = SHIFT_ASHIFTRT; + break; + case LSHIFTRT: + shift_type = SHIFT_LSHIFTRT; + break; + case ASHIFT: + shift_type = SHIFT_ASHIFT; + break; + default: + abort (); + } + + if (GET_CODE (operands[2]) != CONST_INT) + { + /* Indexing by reg, so have to loop and test at top */ + output_asm_insn ("mov %2,%4", operands); + output_asm_insn ("cmp 0,%4", operands); + fprintf (asm_out_file, "\tble .Lle%d\n", loopend_lab); + + /* Get the assembler code to do one shift. */ + get_shift_alg (shift_type, mode, 1, &assembler, &cc_valid); + } + else + { + int n = INTVAL (operands[2]); + enum shift_alg alg; + + /* If the count is negative, make it 0. */ + if (n < 0) + n = 0; + /* If the count is too big, truncate it. + ANSI says shifts of GET_MODE_BITSIZE are undefined - we choose to + do the intuitive thing. */ + else if (n > GET_MODE_BITSIZE (mode)) + n = GET_MODE_BITSIZE (mode); + + alg = get_shift_alg (shift_type, mode, n, &assembler, &cc_valid); + + + switch (alg) + { + case SHIFT_INLINE: + /* Emit one bit shifts. */ + while (n > 0) + { + output_asm_insn (assembler, operands); + n -= 1; + } + + /* Keep track of CC. */ + if (cc_valid) + { + cc_status.value1 = operands[0]; + cc_status.flags |= cc_valid; + } + return ""; + + case SHIFT_SPECIAL: + output_asm_insn (assembler, operands); + + /* Keep track of CC. */ + if (cc_valid) + { + cc_status.value1 = operands[0]; + cc_status.flags |= cc_valid; + } + return ""; + } + + { + fprintf (asm_out_file, "\tmov %d,%s\n", n, + reg_names[REGNO (operands[4])]); + fprintf (asm_out_file, ".Llt%d:\n", loopend_lab); + output_asm_insn (assembler, operands); + output_asm_insn ("add -1,%4", operands); + fprintf (asm_out_file, "\tbne .Llt%d\n", loopend_lab); + return ""; + } + } + + fprintf (asm_out_file, ".Llt%d:\n", loopend_lab); + output_asm_insn (assembler, operands); + output_asm_insn ("add -1,%4", operands); + fprintf (asm_out_file, "\tbne .Llt%d\n", loopend_lab); + fprintf (asm_out_file, ".Lle%d:\n", loopend_lab); + + return ""; +} + +/* Return an RTX to represent where a value with mode MODE will be returned + from a function. If the result is 0, the argument is pushed. */ + +rtx +function_arg (cum, mode, type, named) + CUMULATIVE_ARGS *cum; + enum machine_mode mode; + tree type; + int named; +{ + rtx result = 0; + int size, align; + + /* We only support using 2 data registers as argument registers. */ + int nregs = 2; + + /* Only pass named arguments in registers. */ + if (!named) + return NULL_RTX; + + /* Figure out the size of the object to be passed. We lie and claim + PSImode values are only two bytes since they fit in a single + register. */ + if (mode == BLKmode) + size = int_size_in_bytes (type); + else if (mode == PSImode) + size = 2; + else + size = GET_MODE_SIZE (mode); + + /* Figure out the alignment of the object to be passed. */ + align = size; + + cum->nbytes = (cum->nbytes + 1) & ~1; + + /* Don't pass this arg via a register if all the argument registers + are used up. */ + if (cum->nbytes + size > nregs * UNITS_PER_WORD) + return 0; + + switch (cum->nbytes / UNITS_PER_WORD) + { + case 0: + result = gen_rtx (REG, mode, 0); + break; + case 1: + result = gen_rtx (REG, mode, 1); + break; + default: + result = 0; + } + + return result; +} + +/* Return the number of registers to use for an argument passed partially + in registers and partially in memory. */ + +int +function_arg_partial_nregs (cum, mode, type, named) + CUMULATIVE_ARGS *cum; + enum machine_mode mode; + tree type; + int named; +{ + int size, align; + + /* We only support using 2 data registers as argument registers. */ + int nregs = 2; + + return 0; + /* Only pass named arguments in registers. */ + if (!named) + return 0; + + /* Figure out the size of the object to be passed. */ + if (mode == BLKmode) + size = int_size_in_bytes (type); + else if (mode == PSImode) + size = 2; + else + size = GET_MODE_SIZE (mode); + + /* Figure out the alignment of the object to be passed. */ + align = size; + + cum->nbytes = (cum->nbytes + 1) & ~1; + + /* Don't pass this arg via a register if all the argument registers + are used up. */ + if (cum->nbytes > nregs * UNITS_PER_WORD) + return 0; + + if (cum->nbytes + size <= nregs * UNITS_PER_WORD) + return 0; + + /* Don't pass this arg via a register if it would be split between + registers and memory. */ + if (type == NULL_TREE + && cum->nbytes + size > nregs * UNITS_PER_WORD) + return 0; + + return (nregs * UNITS_PER_WORD - cum->nbytes) / UNITS_PER_WORD; +} + +char * +output_tst (operand, insn) + rtx operand, insn; +{ + + rtx temp; + int past_call = 0; + + /* Only tst insns using address registers can be optimized. */ + if (REGNO_REG_CLASS (REGNO (operand)) != ADDRESS_REGS) + return "cmp 0,%0"; + + /* If testing an address register against zero, we can do better if + we know there's a register already holding the value zero. First + see if a global register has been set to zero, else we do a search + for a register holding zero, if both of those fail, then we use a + compare against zero. */ + if (zero_dreg || zero_areg) + { + rtx xoperands[2]; + xoperands[0] = operand; + xoperands[1] = zero_dreg ? zero_dreg : zero_areg; + + output_asm_insn ("cmp %1,%0", xoperands); + return ""; + } + + /* We can save a byte if we can find a register which has the value + zero in it. */ + temp = PREV_INSN (insn); + while (temp) + { + rtx set; + + /* We allow the search to go through call insns. We record + the fact that we've past a CALL_INSN and reject matches which + use call clobbered registers. */ + if (GET_CODE (temp) == CODE_LABEL + || GET_CODE (temp) == JUMP_INSN + || GET_CODE (temp) == BARRIER) + break; + + if (GET_CODE (temp) == CALL_INSN) + past_call = 1; + + if (GET_CODE (temp) == NOTE) + { + temp = PREV_INSN (temp); + continue; + } + + /* It must be an insn, see if it is a simple set. */ + set = single_set (temp); + if (!set) + { + temp = PREV_INSN (temp); + continue; + } + + /* Are we setting a register to zero? + + If it's a call clobbered register, have we past a call? */ + if (REG_P (SET_DEST (set)) + && SET_SRC (set) == CONST0_RTX (GET_MODE (SET_DEST (set))) + && !reg_set_between_p (SET_DEST (set), temp, insn) + && (!past_call + || !call_used_regs[REGNO (SET_DEST (set))])) + { + rtx xoperands[2]; + xoperands[0] = operand; + xoperands[1] = SET_DEST (set); + + output_asm_insn ("cmp %1,%0", xoperands); + return ""; + } + temp = PREV_INSN (temp); + } + return "cmp 0,%0"; +} + +/* Return nonzero if OP is a valid operand for a {zero,sign}_extendpsisi + instruction. + + It accepts anything that is a general operand or the sum of the + stack pointer and a general operand. */ +extendpsi_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return (general_operand (op, mode) + || (GET_CODE (op) == PLUS + && XEXP (op, 0) == stack_pointer_rtx + && general_operand (XEXP (op, 1), VOIDmode))); +} |